【问题标题】:Problem during isolated controller unit testing / NestJS / JestJS / Typescript隔离控制器单元测试/NestJS/JestJS/Typescript 期间的问题
【发布时间】:2021-09-20 15:35:56
【问题描述】:

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

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

create-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);
  }
}

create-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 方法初始化模拟服务。

任何帮助表示赞赏!提前致谢。

【问题讨论】:

  • 你有什么理由使用moduleRef.resolve而不是moduleRef.get
  • @JayMcDoniel resolve 看起来更简单 :) 我相信在这种情况下获得一个唯一的提供者实例很好。顺便说一句,我已经尝试过 .get 并且没有任何变化。
  • resolve 通常适用于 TRANSIENTREQUEST 范围内的提供程序,虽然我相信它可以SINGLETON 一起使用,但通常最好使用正确的 API为它。
  • @JayMcDoniel 同意,我会深入探讨这个话题,谢谢

标签: typescript unit-testing jestjs mocking nestjs


【解决方案1】:

啊,我明白这里发生了什么。在createUserSymbol 的自定义提供程序中,您正在使用为CreateUserService 创建的模拟,但是这个模拟是工厂,而不是直接值,所以您应该使用useValue 而不是useFactory,这样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();

这种方法也可以测试模块不可知论,因此如果您出于某种原因决定更改库,则更容易交换内容

【讨论】:

  • 感谢您的回复。在第一种情况下,我应该在 useFactory 函数中自己调用 CreateUserService 构造函数吗?这里的困难在于我已经在服务中注入了 repo。我是否还需要模拟存储库来调用构造函数???此外,我还能够使用 jest.mock 实现所需的行为,并且:providers: [{provide: createUserSymbol, useClass: CreateUserService,},], 通过这种设置,它实际上调用了函数的模拟版本,我可以轻松地窥探到它。
  • 在您的第一个场景中,它不是构造函数。这只是一个工厂功能。没有要调用的 new。传递这个自定义模拟的美妙之处在于它不应该真正依赖于原始构造函数是什么。因为 createUserSymbol 被嘲笑,这就是所有必要的。 I have a lot of examples here if that helps
猜你喜欢
  • 2017-09-02
  • 2013-05-28
  • 1970-01-01
  • 2015-06-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多