You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Jest测试NestJS项目时@IsValidMobileNumber装饰器报错求助

NestJS Jest测试报错:TypeError: IsValidMobileNumber is not a function

问题概述

运行jest ./auth.controller.spec.ts时测试套件启动失败,报错TypeError: (0 , decorator_1.IsValidMobileNumber) is not a function,该错误出现在src/user/dto/client-validate.param.dto.ts第30行的自定义装饰器@IsValidMobileNumber()调用处,该装饰器导出自src/_core/decorator/index.ts。

完整错误栈

> jest ./auth.controller.spec.ts

 FAIL  src/auth/auth.controller.spec.ts
  ● Test suite failed to run

    TypeError: (0 , decorator_1.IsValidMobileNumber) is not a function

      28 |   @IsNotEmpty()
      29 |   @IsString()
    > 30 |   @IsValidMobileNumber()
         |                       ^
      31 |   mobileNumber: string;
      32 | }
      33 |

      at Object.<anonymous> (src/user/dto/client-validate.param.dto.ts:30:23)
      at Object.<anonymous> (src/user/controller/client.controller.ts:95:1)
      at Object.<anonymous> (src/user/user.module.ts:4:1)
      at Object.<anonymous> (src/user/index.ts:2:1)
      at Object.<anonymous> (src/_core/decorator/is-email-unique.decorator.ts:10:1)
      at Object.<anonymous> (src/_core/decorator/index.ts:11:1)
      at Object.<anonymous> (src/auth/auth.controller.ts:3:1)
      at Object.<anonymous> (src/auth/auth.controller.spec.ts:2:1)

相关配置

package.json中的Jest配置

"jest": {
    "moduleFileExtensions": [
      "js",
      "json",
      "ts"
    ],
    "roots": [
      "src"
    ],
    "moduleNameMapper": {
      "^@core/(.*)$": "<rootDir>/src/_core/$1",
      "^@module/activity/(.*)$": "<rootDir>/src/activity/$1",
      "^@module/auth$": "<rootDir>/src/auth/index",
      "^@module/role/(.*)$": "<rootDir>/src/role/$1",
      "^@module/user/(.*)$": "<rootDir>/src/user/$1",
      "^@module/health/(.*)$": "<rootDir>/src/health/$1",
      "^@module/notification/(.*)$": "<rootDir>/src/notification/$1",
      "^@module/report/(.*)$": "<rootDir>/src/report/$1",
      "^@module/file/(.*)$": "<rootDir>/src/file/$1",
    },
    "testRegex": ".*\\.spec\\.ts$",
    "transform": {
      "^.+\\.(t|j)s$": "ts-jest"
    },
    "collectCoverageFrom": [
      "**/*.(t|j)s"
    ],
    "coverageDirectory": "../coverage",
    "testEnvironment": "node"
  }

tsconfig.json路径配置

{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "es2017",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true,
    "skipLibCheck": true,
    "strictNullChecks": false,
    "noImplicitAny": false,
    "strictBindCallApply": false,
    "forceConsistentCasingInFileNames": false,
    "noFallthroughCasesInSwitch": false,
    "paths": {
      "@core/config": [
        "src/_core/config/index"
      ],
      "@core/database": [
        "src/_core/database/index"
      ],
      "@core/email": [
        "src/_core/email/index"
      ],
      "@core/enum": [
        "src/_core/enum/index"
      ],
      "@core/guard": [
        "src/_core/guard/index"
      ],
      "@core/interceptor": [
        "src/_core/interceptor/index"
      ],
      "@core/interface": [
        "src/_core/interface/index"
      ],
      "@core/middleware": [
        "src/_core/middleware/index"
      ],
      "@core/notification": [
        "src/_core/notification/index"
      ],
      "@core/strategy": [
        "src/_core/strategy/index"
      ],
      "@core/utils": [
        "src/_core/utils/index"
      ],
      "@core/logger": [
        "src/_core/logger/index"
      ],
      "@core/service": [
        "src/_core/service/index"
      ],
      "@core/decorator": [
        "src/_core/decorator/index"
      ],
      "@core/event": [
        "src/_core/event/index"
      ],
      "@core/swagger": [
        "src/_core/swagger/index"
      ],
      "@core/error": [
        "src/_core/error/index"
      ],
      "@module/activity/*": [
        "src/activity/*"
      ],
      "@module/auth": [
        "src/auth/index"
      ],
      "@module/role/*": [
        "src/role/*"
      ],
      "@module/user/*": [
        "src/user/*"
      ],
      "@module/health/*": [
        "src/health/*"
      ],
      "@module/notification/*": [
        "src/notification/*"
      ],
      "@module/report/*": [
        "src/report/*"
      ],
      "@module/address/*": [
        "src/address/*"
      ],
      "@module/file/*": [
        "src/file/*"
      ],
      "@module/whitelist/*": [
        "src/whitelist/*"
      ],
    }
  }
}

根因排查与解决方案

1. 循环依赖问题(核心原因)

从错误栈的依赖链可看出明显的循环引用:
auth.controller.spec.ts → auth.controller.ts → _core/decorator/index.ts → is-email-unique.decorator.ts → user/index.ts → user.module.ts → client.controller.ts → client-validate.param.dto.ts → 回到IsValidMobileNumber装饰器

解决方法:

  • 重构代码打破循环:将is-email-unique.decorator.ts中对user模块的依赖迁移到服务层,或者使用NestJS的forwardRef来延迟依赖解析
  • 调整导入方式:避免导入整个模块,改为导入具体的服务或类,减少耦合

2. 装饰器导出/导入错误

检查点:

  • 确认is-valid-mobile-number.decorator.ts中装饰器的导出方式是否正确,需使用命名导出:
    export function IsValidMobileNumber() {
      // 装饰器逻辑
    }
    
  • 确认src/_core/decorator/index.ts是否正确聚合导出该装饰器:
    export { IsValidMobileNumber } from './is-valid-mobile-number.decorator';
    // 或使用 export * from './is-valid-mobile-number.decorator';
    

3. Jest路径映射不匹配

tsconfig中@core/decorator映射到src/_core/decorator/index,但Jest的moduleNameMapper中仅配置了^@core/(.*)$,会导致Jest解析@core/decorator时指向目录而非index.ts文件。

解决方法:
在Jest的moduleNameMapper中添加单独的映射规则:

"moduleNameMapper": {
  // 原有规则...
  "^@core/decorator$": "<rootDir>/src/_core/decorator/index",
}

内容的提问来源于stack exchange,提问作者Koala

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 13:29:51