简体   繁体   中英

NestJS Testing: decorator is not a function

My dependencies:

"@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",

I have a custom decorator:

import { Inject } from "@nestjs/common";

import { DATABASE_SERVICE } from "./database.constants";

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

I use it in constructor for a service I'm willing to cover with tests:

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

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

here is how I setup testing module:

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 is a module with @Global() decorator and includes MyService in providers/exports.

When I try to execute my test, I get error:

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)

I followed the trace and steps as you can see above:

  • Import file with service in test: import { MyService } from "../../../src/shared/services/my.service";
  • Raise error at @InjectDatabase() in constructor of service

If I switch from @InjectDatabase to @Inject(DATABASE_SERVICE) :

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

I get another error:

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.

I tried to setup testing module like this:

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

But it didn't help, same issue:

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

My tsconfig.json compiler options:

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

I have in jest.config.js :

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

This file has only import of reflect lib:

import 'reflect-metadata';

I tried to add to my.service.ts before MyService declaration:

console.log('InjectDatabase', InjectDatabase)

and received:

InjectDatabase: undefined

So it seems either Jest cannot resolve imports o something is wrong with typescript config.

Decorator function imported in MyService as:

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

My 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/"]
};

Probably moduleNameMapper doesn't resolve correctly "@infra/(.*)": "<rootDir>/libs/infrastructure/src/$1" for "@infra/modules" ?

Outside of tests - everything works as intended.

How can I setup correctly jest/typescript/nest so my custom decorators recognized correctly?

After a better debugging I found that Jest somehow doesn't resolve path aliases with sub folders to module, and instead of loading imported content it return undefined. Settings in jest.config.js:

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

So second line from above doesn't work with Jest. All imports which had path like: import Stuff from "@infra/sub/folder/module" were returning undefined when paths like: import Stuff from "@infra worked like a charm.

I fixed barrel files in @infra lib to correctly export stuff and fixed imports to use shorten path and it helped to resolve the issue.

I'm still curious why setting for Jest: "@infra/(.*)": "<rootDir>/libs/infrastructure/src/$1" under: moduleNameMapper doesn't work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM