I have an AppModule file as follows:
import { Module } from '@nestjs/common'
import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq'
@Module({
imports: [
RabbitMQModule.forRoot(RabbitMQModule, {
exchanges: [
{
name: 'my_rabbit',
type: 'direct',
},
],
uri: process.env.RABBITMQ_URI,
connectionInitOptions: { wait: true },
}),
],
})
export class AppModule {}
I have tried to mock rabbitmq using @golevelup/nestjs-rabbitmq like this:
import { Module } from '@nestjs/common'
import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq'
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
AppModule
],
})
.overrideProvider(AmqpConnection)
.useValue(createMock<AmqpConnection>())
.compile()
})
This is giving me error:
[Nest] 2745 - 24/07/2022, 17:02:54 ERROR [AmqpConnection] Disconnected from RabbitMQ broker (default)
Error: connect ECONNREFUSED 127.0.0.1:5672
If i mock the whole rabbitmq module like:
jest.mock('@golevelup/nestjs-rabbitmq')
I will get errors like:
Nest cannot create the AppModule instance.
The module at index [0] of the AppModule "imports" array is undefined.
Has anyone successfully mocked RabbitMQ? Please assist if possible.
The main issue is that AppModule has the RabbitMQModule, which is trying to connect. overrideProvider does not prevent the RabbitMQModule within the AppModule from instantiating, and hence the error.
There are a few ways to solve this.
The simplest way is to not import AppModule, and re-create the module with whatever imports/providers it has. In this case, there's only RabbitMQModule. It returns a few providers, but typically you only need to provide AmqpConnection. So for this, we only needed to provide a mock like this:
import { AmqpConnection } from '@golevelup/nestjs-rabbitmq'
import { mock } from 'jest-mock-extended'
beforeEach(async () => {
const module = await Test.createTestingModule({
imports: [],
providers: [
{ provide: AmqpConnection, useValue: mock<AmqpConnection>() }
})
.compile()
})
However, in most instances, a module can grow to have a lot of imports and providers. Re-constructing it is tedious, and you want to be able to just import it, and write __mocks__ to allow it to run in the test environment.
You can mock node modules in jest by writing the a manual mock (see https://jestjs.io/docs/manual-mocks).
However, for NestJS modules, it usually very troublesome as you need to read the source code and "re-construct" the Nest module. Sometimes the source code is not straight forward.
In this case, the @golevelup/nestjs-rabbitmq mock looks like this:
File: src/__mocks__/@golevelup/nestjs-rabbitmq.ts
(Note: The jest docs said that __mocks__ should be at the same level with node_modules. But that didn't work for me.)
import { mock } from 'jest-mock-extended'
// create a deeply mocked module
const rmq = jest.createMockFromModule<typeof import('@golevelup/nestjs-rabbitmq')>(
'@golevelup/nestjs-rabbitmq',
)
// all the mocked methods from #createMockFromModule will return undefined
// but in this case, #forRoot needs to return mocked providers
// specifically AmqpConnection, and this is how it is done:
rmq.RabbitMQModule.forRoot = jest.fn(() => ({
module: rmq.RabbitMQModule,
providers: [
{
provide: rmq.AmqpConnection,
useValue: mock<typeof rmq.AmqpConnection>(),
},
],
exports: [rmq.AmqpConnection],
}))
module.exports = rmq
Sometimes you may want to spin up an in-memory instance, or use testcontainer, especially for e2e:
File src/__mocks__/@golevelup/nestjs-rabbitmq.ts
import { AmqpConnection } from '@golevelup/nestjs-rabbitmq'
import { mock } from 'jest-mock-extended'
import { GenericContainer } from 'testcontainers'
const rmq = jest.createMockFromModule<typeof import('@golevelup/nestjs-rabbitmq')>(
'@golevelup/nestjs-rabbitmq',
)
rmq.RabbitMQModule.forRoot = jest.fn(() => ({
module: rmq.RabbitMQModule,
providers: [
{
provide: rmq.AmqpConnection,
useFactory: async () => {
const RABBITMQ_DEFAULT_USER = 'RABBITMQ_DEFAULT_USER'
const RABBITMQ_DEFAULT_PASS = 'RABBITMQ_DEFAULT_PASS'
const PORT = 5672
const rmqContainer = new GenericContainer('rabbitmq:3.11.6-alpine')
.withEnvironment({
RABBITMQ_DEFAULT_USER,
RABBITMQ_DEFAULT_PASS,
})
.withExposedPorts(PORT)
const rmqInstance = await rmqContainer.start()
const port = rmqInstance.getMappedPort(PORT)
return new AmqpConnection({
uri: `amqp://${RABBITMQ_DEFAULT_USER}:${RABBITMQ_DEFAULT_PASS}@localhost:${port}`,
})
},
},
],
exports: [rmq.AmqpConnection],
}))
module.exports = rmq
The same concept can be used to write mocks for stuff like TypeORM, Mongo, Redis etc.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With