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