Search code examples
unit-testingnestjsnestjs-testing

Nestjs overrideProvider vs provider in unit testing


I see two ways of mocking services in NestJS for unit testing, the first one is the same as we define providers in real modules like:

const module = await Test.createTestingModule({
  providers: [
    UserService,
    {
      provide: getRepositoryToken(User),
      useValue: mockUsersRepository,
    }
  ],
}).compile();

And the other way with overrideProvider method. As following:

const module = await Test.createTestingModule({
  imports: [UserModule]
})
.overrideProvider(getRepositoryToken(User))
.useValue(mockUsersRepository)
.compile();

What is the difference?


Solution

  • The difference is pretty simple.

    With the first approach (array of providers), you create custom testing module to test (probably) the UserService.

    With second approach, you use complete module in the very same shape as it is used in the application itself.

    The result is exactly the same - your mock is being injected into the constructor of UserService.

    The first approach is better for small, mostly unit tests, but these tests can be also done without using NestJS test tooling at all (just pass mock manually to the ctor), while the second one does a great job in integration tests.

    Repository is not great example to use to explain, but think about Logger. You are performing some integration tests of 2 or more modules. You do not want to manually create big testing module (which also is breaking the connection with real shape of your modules), but you want to just import your modules which are being tested together and .overrideProvider for Logger with e.g. loggerMock which lets you to assert all logger calls across all tested modules.

    Example:

    @Module({providers: [LoggerService], exports: [LoggerService]})
    export class LoggerModule {}
    
    @Module({imports: [LoggerModule], providers: [FooService]})
    export class FooModule {}
    
    @Module({imports: [LoggerModule], providers: [BarService]})
    export class BarModule {}
    
    @Module({imports: [FooModule, BarModule]}
    export class AppModule {}
    
    // TEST
    const testModule = await Test.createTestingModule({
      import: [AppModule]
    })
      .overrideProvider(LoggerService)
      .useValue(/* your logger mock will be provided in both FooService and BarService and you can easily test all related to logs then */)
      .compile();
    

    I hope it is clear. If not, please leave a comment and I will try to explain more.