Search code examples
typescriptjestjsnestjstypeerrordecorator

When testing NestJS controller using jest, it throwns TypeError on controller's method parameter decorator


Earlier everything worked, but i already have no idea what is wrong. Other tests pass correctly. Problem is only with method parameter decorators. Package dependencies fresh enough.

The error: "TypeError: Cannot read properties of undefined (reading 'prototype')";

Here is controller to test (simplified, but fails):

import {
  Controller, Post, Body,
} from '@nestjs/common';

@Controller('mytest')
export class MyTestController {
  @Post()
  async testMethod(@Body() b: any): Promise<void> {
    return undefined;
  }
}

With @Res and @Req decorators from '@nestjs/common' got the same error. Tried to use custom @required decorator from Typescript docs: The same error.

The test:

import { MyTestController } from './mytest-controller';

test('...', async () => {
  const a = new MyTestController();
  expect(true).toBeTruthy();
});

Test fails during MyTestController is importing after test command was run. So the test is not even reached.

If i remove all parameter decorators, test will pass.

reflect-metadata is imported in jest global setup file. But tried to import directly in the test file: no difference.

I tried:

  • clear my controller test from all dependencies (only controller was left).
  • downgrade nestjs packages
  • run test on another pc
  • downgrade jest packages

Package dependencies:

"dependencies": {
    "@nestjs/common": "^8.2.6",
    "@nestjs/core": "^8.2.6",
    "@nestjs/schematics": "^8.0.5",
    "@types/express": "^4.17.12",
    "@types/jsonwebtoken": "^8.5.1",
    "@types/supertest": "^2.0.11",
    "argon2": "^0.28.3",
    "dotenv": "^14.3.2",
    "express": "^4.17.1",
    "jsonwebtoken": "^8.5.1",
    "reflect-metadata": "^0.1.13",
    "rxjs": "^7.5.2",
    "ts-node": "^10.7.0",
    "typeorm": "^0.2.41",
    "uuid": "^8.3.2"
  },
  "devDependencies": {
    "@babel/cli": "^7.16.8",
    "@babel/core": "^7.16.12",
    "@babel/plugin-proposal-class-properties": "^7.16.7",
    "@babel/plugin-proposal-decorators": "^7.16.7",
    "@babel/plugin-proposal-export-default-from": "^7.16.7",
    "@babel/preset-env": "^7.16.11",
    "@babel/preset-typescript": "^7.16.7",
    "@nestjs/testing": "8.2.6",
    "@types/jest": "^27.4.1",
    "@types/uuid": "^8.3.4",
    "@typescript-eslint/eslint-plugin": "^5.10.1",
    "@typescript-eslint/parser": "^5.10.1",
    "babel-jest": "^27.5.1",
    "babel-plugin-parameter-decorator": "^1.0.16",
    "eslint": "^8.7.0",
    "eslint-config-airbnb-base": "^15.0.0",
    "eslint-plugin-import": "^2.25.4",
    "eslint-plugin-jest": "^26.0.0",
    "jest": "^27.5.1",
    "supertest": "^6.2.2",
    "testcontainers": "^8.6.1",
    "ts-jest": "^27.1.4",
    "typescript": "^4.5.5"
  }

Any ideas?

Tried to unite controller and it's test in one file: got error.

Tried to use decorator without controller (just class) as below, no erorrs.

import 'reflect-metadata';

const requiredMetadataKey = Symbol('required');

function required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
  const existingRequiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
  existingRequiredParameters.push(parameterIndex);
  Reflect.defineMetadata(requiredMetadataKey, existingRequiredParameters, target, propertyKey);
}

class A {
  do(@required par: any): void {
    return undefined;
  }
}

test('...', async () => {
  const a = new A();
  a.do({});
  expect(true).toBeTruthy();
});

So something is wrong with bundle of nestjs controller and any method parameter decorator.

Removal @Controller() and @Post() decorators from MyTestController, but with @Body() decorator: no errors.

I can suppose there is problem with babel-jest, which transforms ts to old js style. My babel config:

module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        targets: {
          "node": "current"
        }
      }
    ],
    "@babel/preset-typescript"
  ],
  plugins: [
    ["@babel/plugin-proposal-decorators", {"legacy": true}],
    ["@babel/plugin-proposal-class-properties"],
    "@babel/plugin-proposal-export-default-from",
    "babel-plugin-parameter-decorator",
  ],
}

But, in my opinion, babel is configured properly


Solution

  • So, i couldn't find a mistake in babel. So i found another transformer named 'ts-jest' and replaced babel-jest with in in the jest config file:

    transform: {
        "\\.ts$": 'ts-jest',
      },
    

    But after that there were errors in jest-setup-after-end, global-setup and global-teardown files about import (like: you cannot use 'import' statement outside the module). I had to convert this files from .js to .ts, add them to tsconfig.json in "include" section and everything became right.