隔离控制器单元测试期间出现问题 / NestJS / JestJS / Typescript

Problem during isolated controller unit testing / NestJS / JestJS / Typescript

我想用模拟服务为我的 http 控制器编写单元测试。 主要思想是模拟服务并为其提供一些模拟实现。

这是我执行此操作的设置(我将跳过一些导入语句并且在我们的上下文装饰器中无用以减少代码行数):

创建-user.http.controller.ts:

export class CreateUserHttpController {
  constructor(
    @Inject(createUserSymbol)
    private readonly createUser: CreateUserService,
  ) {}

  @Post(appRoutes.users.root)
  async create(@Body() body: CreateUserRequest): Promise<IdResponse> {
    const command = new CreateUserCommand({
      email: body.email,
      name: body.name,
      password: body.password,
      role: 'user',
    });

    const id = await this.createUser.createUser(command);

    return new IdResponse(id.value);
  }
}

创建-user.http.controller.spec.ts:

import { Test } from '@nestjs/testing';

import { ID } from 'src/core/value-objects/id.value-object';

import { createUserSymbol } from '../../user.providers';
import { CreateUserHttpController } from './create-user.http.controller';
import { CreateUserService } from './create-user.service';

jest.mock('./create-user.service', () => {
  return {
    CreateUserService: jest.fn().mockImplementation(() => {
      return {
        createUser: () => {
          console.log('ts');
        },
      };
    }),
  };
});

describe('CreateUserHttpController', () => {
  let createUserHttpController: CreateUserHttpController;
  let createUserService: CreateUserService;

  beforeAll(async () => {
    const moduleRef = await Test.createTestingModule({
      controllers: [CreateUserHttpController],
      providers: [
        {
          provide: createUserSymbol,
          useValue: CreateUserService,
        },
      ],
    }).compile();

    createUserHttpController = await moduleRef.resolve(
      CreateUserHttpController,
    );
    createUserService = await moduleRef.resolve(createUserSymbol);
  });

  describe('create', () => {
    it('should call once createUser method in service', async () => {
      const expectedResult = new ID('test');

      jest
        .spyOn(createUserService, 'createUser')
        .mockResolvedValue(expectedResult);

      const result = await createUserHttpController.create({
        email: 'test@mail.ru',
        name: 'test',
        password: 'test',
      });

      expect(createUserService).toHaveBeenCalledTimes(1);
    });
  });
});

使用此设置我遇到了这样的问题: (无法侦测 属性 因为它不是函数;未定义而是给定的

我已经尝试 console.log 模拟服务实例来检查是否有 'createUser' 属性。而且好像没有。

我已经深入检查了 jest 文档,尤其是这里 - jest doc for mocking non-default class exports。我花了将近 6 个小时并尝试了所有可用选项,但结果仍然相同。

我还查看了此处的指南 - medium guide。这个幸运的家伙似乎有相同的设置,没有任何问题。

因此,从我的角度来看,由于某种原因,jest.mock 函数似乎未使用 createUser 方法初始化模拟服务。

感谢任何帮助!提前致谢。

啊,我明白这是怎么回事了。在 createUserSymbol 的自定义提供程序中,您使用的是为 CreateUserService 创建的模拟,但是这个模拟是一个工厂,而不是直接值,因此您应该使用 [= 而不是 useValue 15=] 以便 Nest 最终调用工厂函数。如果你删除 jest.fn().mockImplementation() 然后看看那里会有什么而不​​是

{
  CreateUserService: () => {
    return {
      createUser: () => {
        console.log('ts');
      }
    }
  }
}

因此,要获取返回的对象,您需要先调用该函数,即 CreateUserService(),或者如前所述,您可以使用 useFactory,Nest 会为您调用该方法。


另一种选择是完全放弃 jest.mock(),只将自定义值传递给 Nest 进行模拟,例如

const moduleFixture = await Test.createTestingModule({
  controllers: [CreateUserController],
  providers: [
    {
      provide: createUserSymbol,
      useValue: {
        createUse: jest.fn().mockImplementation(() => console.log('ts'))
      }
    }
  ],
}).compile();

这种方法也可以测试模块不可知论,因此无论出于何种原因决定更改库,都可以更轻松地进行交换