NestJS 测试:装饰器不是函数

Posted

技术标签:

【中文标题】NestJS 测试:装饰器不是函数【英文标题】:NestJS Testing: decorator is not a function 【发布时间】:2021-01-20 16:40:26 【问题描述】:

我的依赖:

"@nestjs/common": "7.4.4",
"@nestjs/core": "7.4.4",
"typescript": "4.0.2",
"jest": "26.4.2",
"jest-junit": "11.1.0",
"ts-jest": "26.3.0",

我有一个自定义装饰器:

import  Inject  from "@nestjs/common";

import  DATABASE_SERVICE  from "./database.constants";

export const InjectDatabase = () => Inject(DATABASE_SERVICE);

我在构造函数中将它用于我愿意通过测试覆盖的服务:

import 
  DatabaseService,
  InjectDatabase
 from "@infra/modules";
import  Injectable  from "@nestjs/common";

@Injectable()
export class MyService 
  constructor(
    @InjectDatabase() private readonly db: DatabaseService
  ) 

这是我设置测试模块的方式:

import  Test  from "@nestjs/testing";

import  MyService  from "../../../src/shared/services/my.service";
import  SharedModule  from "../../../src/shared/shared.module";

describe(`MyService`, () => 
  let myService: MyService;

  beforeEach(async () => 
    const moduleRef = await Test.createTestingModule(
      imports: [SharedModule]
    ).compile();

    myService = moduleRef.get<MyService>(MyService);
  );
  test("tests", () => 
    // console.log("test");
  );
);

SharedModule 是一个带有@Global() 装饰器的模块,并在providers/exports 中包含MyService

当我尝试执行测试时,出现错误:

TypeError: modules_1.InjectDatabase is not a function

      51 | 
      52 |   constructor(
    > 53 |     @InjectDatabase() private readonly adb_db: DatabaseService,
         |      ^
      56 |   ) 

      at Object.<anonymous> (apps/workers/my/src/shared/services/my.service.ts:53:6)
      at Object.<anonymous> (apps/workers/my/test/shared/services/my.service.unit.test.ts:8:1)

我按照上面的跟踪和步骤进行操作:

在测试中使用服务导入文件: import MyService from "../../../src/shared/services/my.service"; 在服务构造函数中的 @InjectDatabase() 处引发错误

如果我从@InjectDatabase 切换到@Inject(DATABASE_SERVICE)

@Injectable()
export class MyService 
  constructor(
    @Inject(DATABASE_SERVICE) private readonly db: DatabaseService
  ) 

我收到另一个错误:

Error: Nest can't resolve dependencies of the CacheService (?). Please make sure that the argument dependency at index [0] is available in the RootTestModule context.

我尝试这样设置测试模块:

    const moduleRef = await Test.createTestingModule(
      imports: [DatabaseModule],
      providers: [
        
          provide: DATABASE_SERVICE,
          useClass: DatabaseService
        ,
        MyService
      ]
    ).compile();

但它没有帮助,同样的问题:

Error: Nest can't resolve dependencies of the CacheService (?).

我的tsconfig.json 编译器选项:

  "compilerOptions": 
    "target": "es2018",
    "module": "commonjs",
    "outDir": "dist/",
    "esModuleInterop": true,
    "strict": true,
    "sourceRoot": "/",
    "sourceMap": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "strictPropertyInitialization": false,
    "removeComments": true,
    "resolveJsonModule": true
  

我在jest.config.js:

setupFiles: ["./jest-setup-file.ts"],

此文件仅导入反射库:

import 'reflect-metadata';

我尝试在MyService 声明之前添加到my.service.ts

console.log('InjectDatabase', InjectDatabase)

并收到:

InjectDatabase: undefined

因此,Jest 似乎无法解析导入,或者 typescript 配置有问题。

MyService中导入的装饰器函数为:

import 
  DatabaseService,
  InjectDatabase
 from "@infra/modules";

我的jest.config.js

module.exports = 
  bail: 0,
  verbose: true,
  preset: "ts-jest",
  testEnvironment: "node",
  roots: ["<rootDir>/libs", "<rootDir>/apps"],
  testRegex: ".*\\.test\\.ts$",
  testPathIgnorePatterns: ["/node_modules/", "/dist/", "/build/"],
  moduleFileExtensions: ["ts", "js", "json"],
  moduleNameMapper: 
    "@common": "<rootDir>/libs/common/src",
    "@common/(.*)": "<rootDir>/libs/common/src/$1",
    "@infra": "<rootDir>/libs/infrastructure/src",
    "@infra/(.*)": "<rootDir>/libs/infrastructure/src/$1"
  ,
  transform: 
    "^.+\\.ts?$": "ts-jest"
  ,
  setupFiles: ["./jest-setup-file.ts"],
  collectCoverage: true,
  coverageThreshold: 
    global: 
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    
  ,
  coverageReporters: ["json", "lcov", "text", "clover"],
  coveragePathIgnorePatterns: ["/node_modules/"]
;

可能moduleNameMapper 无法正确解析"@infra/(.*)": "&lt;rootDir&gt;/libs/infrastructure/src/$1""@infra/modules"

在测试之外 - 一切都按预期工作。

如何正确设置 jest/typescript/nest 以便正确识别我的自定义装饰器?

【问题讨论】:

我不明白您编写自定义装饰器的方式。您是否尝试过 createParamDecorator 方法,如文档中所述? docs.nestjs.com/custom-decorators @Seraf 好吧,我尝试这样做,但似乎开玩笑无法使用该函数解析模块的路径,并以未定义结束。 你能提供最低限度的复制吗?在使用自定义装饰器并对其进行测试之前,我没有遇到任何问题。 【参考方案1】:

经过更好的调试后,我发现 Jest 无法将带有子文件夹的路径别名解析为模块,而不是加载导入的内容,而是返回未定义的内容。 jest.config.js 中的设置:

    "@infra": "<rootDir>/libs/infrastructure/src",
    "@infra/(.*)": "<rootDir>/libs/infrastructure/src/$1"

所以上面的第二行不适用于 Jest。 所有具有以下路径的导入: import Stuff from "@infra/sub/folder/module" 正在返回undefined 当路径像: import Stuff from "@infra 像魅力一样工作。

我修复了 @infra lib 中的桶文件以正确导出内容并修复导入以使用缩短路径,这有助于解决问题。

我仍然很好奇为什么要设置 Jest: "@infra/(.*)": "&lt;rootDir&gt;/libs/infrastructure/src/$1" 在下面: moduleNameMapper 不工作。

【讨论】:

以上是关于NestJS 测试:装饰器不是函数的主要内容,如果未能解决你的问题,请参考以下文章

NestJS 自定义装饰器返回未定义

NestJs:确保你的班级用合适的装饰器装饰

NestJS 的自定义路由装饰器

NestJS - 如何使用 @Body() 装饰器访问帖子正文?

nestjs Response装饰器 使用

NestJS:如何使用另一个装饰器自动记录@MessagePattern 的输入数据?