如何在Jest中Mock NestJS的RabbitMQ模块?求解决方案
Mock @golevelup/nestjs-rabbitmq 解决连接失败问题
问题场景
现有AppModule代码如下:
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 {}
尝试两种Mock方式均失败:
方式一:覆盖AmqpConnection提供者
代码:
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() })
报错:
[Nest] 2745 - 24/07/2022, 17:02:54 ERROR [AmqpConnection] Disconnected from RabbitMQ broker (default) Error: connect ECONNREFUSED 127.0.0.1:5672
原因:RabbitMQModule.forRoot在模块初始化阶段就会尝试建立真实连接,overrideProvider的操作时机太晚,无法阻止连接请求。
方式二:直接Mock整个模块
代码:
jest.mock('@golevelup/nestjs-rabbitmq')
报错:
Nest cannot create the AppModule instance. The module at index [0] of the AppModule "imports" array is undefined.
原因:jest.mock会把模块的所有导出替换为undefined,导致RabbitMQModule.forRoot无法返回有效模块实例。
正确解决方案
方案1:Mock RabbitMQModule的forRoot方法
通过jest.mock自定义模块导出,让forRoot返回空Nest模块,同时MockAmqpConnection类:
import { Test } from '@nestjs/testing'; import { AppModule } from './app.module'; import { AmqpConnection } from '@golevelup/nestjs-rabbitmq'; import { createMock } from '@golevelup/ts-jest'; jest.mock('@golevelup/nestjs-rabbitmq', () => ({ RabbitMQModule: { forRoot: jest.fn().mockReturnValue({ module: class {}, }), }, AmqpConnection: jest.fn(), })); describe('AppModule', () => { beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ imports: [AppModule], }) .overrideProvider(AmqpConnection) .useValue(createMock<AmqpConnection>()) .compile(); }); // 测试用例编写... });
方案2:手动构建测试依赖,避免真实连接
直接在测试模块中导入业务模块,配合Mock配置的RabbitMQModule,跳过真实连接逻辑:
import { Test } from '@nestjs/testing'; import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq'; import { createMock } from '@golevelup/ts-jest'; import { AmqpConnection } from '@golevelup/nestjs-rabbitmq'; import { YourService } from './your.service'; describe('YourService', () => { let service: YourService; let amqpConnection: jest.Mocked<AmqpConnection>; beforeEach(async () => { amqpConnection = createMock<AmqpConnection>(); const module: TestingModule = await Test.createTestingModule({ imports: [ RabbitMQModule.forRoot(RabbitMQModule, { exchanges: [{ name: 'my_rabbit', type: 'direct' }], uri: 'amqp://guest:guest@localhost:5672', connectionInitOptions: { wait: false }, // 禁用连接等待 }), ], providers: [YourService], }) .overrideProvider(AmqpConnection) .useValue(amqpConnection) .compile(); service = module.get<YourService>(YourService); }); it('should trigger RabbitMQ publish', () => { service.sendTestMessage(); expect(amqpConnection.publish).toHaveBeenCalled(); }); });
方案3:用overrideModule替换原RabbitMQModule
若要保留AppModule结构,可通过overrideModule替换掉原有的RabbitMQModule导入:
import { Test } from '@nestjs/testing'; import { AppModule } from './app.module'; import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq'; import { createMock } from '@golevelup/ts-jest'; import { AmqpConnection } from '@golevelup/nestjs-rabbitmq'; describe('AppModule', () => { beforeEach(async () => { const mockRabbitModule = RabbitMQModule.forRoot(RabbitMQModule, { exchanges: [{ name: 'my_rabbit', type: 'direct' }], uri: 'amqp://mock:mock@localhost:5672', connectionInitOptions: { wait: false }, }); const module: TestingModule = await Test.createTestingModule({ imports: [AppModule], }) .overrideModule(RabbitMQModule) .useModule(mockRabbitModule) .overrideProvider(AmqpConnection) .useValue(createMock<AmqpConnection>()) .compile(); }); });
内容的提问来源于stack exchange,提问作者Calvintwr
相关产品推荐
相关产品推荐

