nestjs模拟导入模块

时间:2020-03-19 11:49:42

标签: testing jestjs nestjs

我正按照官方Nestjs documentation所述,使用AuthModule测试我的supertest控制器 HTTP层。该模块使用从EmailService导入的EmailModule

我知道您可以按以下方式覆盖提供程序:

const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [
        AuthModule,
      ],
    })
      .overrideProvider(EmailService)
      .useValue(buildMock(EmailService))

但是这不起作用,我假设是因为EmailModule被导入到AuthModule中。一种解决方法是.overrideProvider(...).useValue(...)中的EmailModule中的每个提供程序,但是这种无意义的做法是,我不得不模拟EmailModule导入的模块。

当我进行AuthModule的e2e测试时,我真的不关心EmailModule的工作方式。我需要模拟的是EmailService,并确保我的身份验证模块与该服务正确交互。

不幸的是,Test.createTestingModule({})没有.overrideModule()方法。

我尝试开玩笑地嘲笑EmailModule

jest.mock('../../src/email/email.module.ts', () => {
  @Module({})
  class EmailModule {

  }

  return EmailModule;
});

但我收到此错误:

Error: Nest cannot create the module instance. Often, this is because of a circular dependency between modules. Use forwardRef() to avoid it.

(Read more: https://docs.nestjs.com/fundamentals/circular-dependency)
Scope [RootTestModule -> AppModule]

有人知道如何实现吗?

1 个答案:

答案 0 :(得分:2)

首先,我认为您对自己编写的测试类型有错误的印象。

让我们把事情弄清楚:

  • NestJs 使用 supertest 和 jest 进行 E2E 测试
  • NestJs 使用简单的笑话进行单元测试

因此,很可能,如果您正在使用 supertest 编写测试,则很可能您正在编写 E2E 测试,并且通常情况下,您不想在 E2E\API 测试中模拟事物.

nestjs\jest 单元测试的模拟

由于我们正在编写单元测试,因此我们可以在不使用原始 EmailModule 的情况下模拟您的依赖项。这是因为我们改写了 Test.createTestingModule。我们可以直接从 EmailModule 导入使用的服务。

假设我有以下模块:

@Module({
  imports: [ UserModule, RepoModule, PullRequestModule ],
  controllers: [WebhookEventManagerController],
  providers: [
    WebhookEventManagerService
  ]
})
export class WebhookEventManagerModule {}

假设我在 UserService 中使用了 WebhookEventManagerService。因此,我创建了一个包含真实 WebhookEventManagerService 的测试模块,并为 nestjs 注入的服务提供模拟而不使用应用程序的真实模块

const UserServiceMock = {
  create: (...anything) => user,
  findByName: noop
}
beforeEach(async () => {
  const module: TestingModule = await Test.createTestingModule({
    providers: [
      WebhookEventManagerService,
      {
        provide: UserService,
        useValue: UserServiceMock
      }
    ]
  }).compile();

  service = module
    .get<WebhookEventManagerService>(WebhookEventManagerService);
});

it('test example', async () => {
  jest.spyOn(UserServiceMock, 'findByName')
        .mockImplementation(() => Promise.resolve());
  await service.run();

  expect(UserServiceMock.findByName).toHaveBeenCalledWith({});
  expect(UserServiceMock.findByName).toHaveBeenCalledTimes(1);
});

说明

  • noop 是从 lodash 导入的,但是您可以在 useValue 中随意实现模拟,因为在测试中运行 jest.spyOn 时您正在模拟特定方法.
  • 您绝对可以使用 UserServiceMock 实现 jest.fn 并使其在全球范围内工作。这很可能是我能想到的最少设置的方法。
  • 我通常不直接为 .module.ts 文件编写单元测试。但是我在运行整个应用程序时会在 E2E\API 测试中测试它们。此外,在 E2E\API 测试中,无论我正在测试服务器的哪个部分,我都只是在创建完整的服务器端应用程序。它总是看起来像这样:
    beforeEach(async () => {
      const moduleFixture: TestingModule = await Test.createTestingModule({
        imports: [AppModule]
      }).compile();
    
      app = moduleFixture.createNestApplication();
      await app.init();
      server = app.getHttpServer();
    });
    

对于 API 测试(位于 <root>/test 中),不要模拟一般情况。

Example for unit-tests Example for API\E2E-tests