Nestjs overrideProvider vs provider in unit testing

Solution 1:

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.

Solution 2:

So let me try to explain it this way: overrideProvider is useful when you've imported an entire module and need to override something it has as a provider. A use case, like the answer mentioned, would be overriding a logger. So say you have

const modRef = await Test.createTestingModule({
  import: [AuthModule]
}).compile();

And assume that AuthModule has imports: [ LoggerModule ]. In our test, we don't really want to see all the logs created, but we can't provide a custom provider for the LoggerService because it's being imported and used via the LoggerModule (overriding an injection token isn't really a common practice). So to provide our own implementation forLoggerService (let's say we just need a noop log method) we can do the following

const modRef = await Test.createTestingModule({
  import: [AuthModule]
})
  .overrideProvider(LoggerService)
  .useValue({ log: () => { /* noop */ } })
  .compile();

And now when our AuthService calls this.logger.log() it will just call this noop and be done with it.

On the flip side, if we're doing unit testing, usually you don't need to overrideProvider because you just set up the provider and the custom provider directly in the testing module's metadata and use that.

The overrideProvider is really useful when you have to use imports (like integration and e2e tests), otherwise, generally, it's better to use a custom provider