nestjs mock imported module

Viewed 6833

I'm trying to test my AuthModule controller HTTP layer with supertest as described in the official Nestjs documentation. This module uses an EmailService imported from an EmailModule.

I know you can override providers as follows:

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

but this doesn't work, I'm assuming because the EmailModule is imported in the AuthModule. A workaround is to .overrideProvider(...).useValue(...) every provider in the EmailModule, but this non sense as I'd then have to also mock modules imported by the EmailModule.

When I'm e2e testing the AuthModule, I honestly don't care about how the EmailModule works. All I should need to mock is the EmailService and make sure my auth module interacts with that service properly.

The Test.createTestingModule({}) doesn't have a .overrideModule() method, unfortunately.

I tried mocking the EmailModule with jest:

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

  }

  return EmailModule;
});

but I get this error:

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]

Does anyone know how this can be achieved ?

3 Answers

The way I solved this is by creating a TestModule that is global:

    @Global()
    @Module({
      providers: [
        {
          provide: EmailService,
          useValue: {} // mock
        },
      ],
    })
    class TestModule {}

Import the TestModule at the beginning of your testing module:

    const module = await Test.createTestingModule({
      imports: [
        TestModule,
        AuthModule,
        // ...
      ]
    }).compile();

That way, the EmailService is provided globally and you don't need to import the EmailModule anymore.

Global modules are explained here.

I spent quite a long time to try figure how to mock a full NestJS module. Below is what works for me.

// Create a valid NestJS module to be used in place of the one we want to mock
@Module({})
class MockModule {}

// Identify the Module you want to mock and mock it
jest.mock('module-to-mock', () => {
  return {
    ModuleToMockName: {
      forRootAsync: jest.fn().mockImplementation(() => MockModule),
    }
  };
});

Basically we provide NestJS with a valid module that in this case does nothing but could be coded to behave in a predictable / testable way.

did you try just to provide the EmailService in the testing module? It would look something like this:

const module: TestingModule = await Test.createTestingModule({
  controllers: [AuthController],
  providers: [
    EmailService        
  ],
}).compile();

I think you can just skip importing the entire EmailModule and just provide what is needed

Related