如何为NestJS单仓储模块正确Mock MongoDB进行单元测试
问题解决:NestJS通用MongoRepository的单元测试方案
一、当前测试的依赖缺失问题修复
你的测试代码无法解析依赖,核心是未提供MongoRepository和forwardRef引入的ChannelsModule相关依赖。下面分两种主流方案解决:
方案1:Mock MongoRepository(纯单元测试,隔离数据库)
单元测试的核心是验证业务逻辑而非数据库交互,直接Mock通用仓储的方法即可,无需启动真实数据库:
// 先定义测试用的模拟数据 const mockConversation = { _id: 'conv-1', title: 'Test Conversation' }; beforeAll(async () => { const app: TestingModule = await Test.createTestingModule({ providers: [ ConversationsService, // 替换你之前误写的AppService // Mock Conversation Model(如果服务直接注入Model的话) { provide: getModelToken(Conversation.name), useValue: jest.fn(), }, // Mock通用MongoRepository的所有调用方法 { provide: MongoRepository, useValue: { findOne: jest.fn().mockResolvedValue(mockConversation), findOneById: jest.fn().mockResolvedValue(mockConversation), updateOneById: jest.fn().mockResolvedValue({ modifiedCount: 1 }), // 按需添加服务中用到的其他仓储方法 }, }, // 处理forwardRef的ChannelsModule:Mock其导出的核心服务 { provide: ChannelsService, useValue: { getChannelById: jest.fn().mockResolvedValue({ _id: 'chan-1' }), // 按需添加服务依赖的ChannelsService方法 }, }, ], }).compile(); conversationService = app.get<ConversationsService>(ConversationsService); });
方案2:内存MongoDB集成测试(验证仓储真实逻辑)
如果需要测试仓储与数据库的交互逻辑,用MongoMemoryServer搭建临时数据库,同时正确配置Nest模块:
let mongod: MongoMemoryServer; let mongoConnection: Connection; beforeAll(async () => { mongod = await MongoMemoryServer.create(); const uri = mongod.getUri(); const app: TestingModule = await Test.createTestingModule({ imports: [ // 配置Mongoose根连接,指向内存Mongo MongooseModule.forRoot(uri), // 注册Conversation实体的Model MongooseModule.forFeature([{ name: 'Conversation', schema: ConversationSchema }]), // 导入通用MongoRepository模块 MongoRepository, // 处理forwardRef的ChannelsModule:要么导入其测试模块,要么Mock forwardRef(() => ({ module: class MockChannelsModule {}, providers: [{ provide: ChannelsService, useValue: {} }], exports: [ChannelsService], })), ], providers: [ConversationsService], }).compile(); conversationService = app.get<ConversationsService>(ConversationsService); mongoConnection = app.get<Connection>(Connection); }); // 清理测试数据 afterAll(async () => { await mongoConnection.dropDatabase(); await mongoConnection.close(); await mongod.stop(); }); afterEach(async () => { const collections = mongoConnection.collections; for (const key in collections) { await collections[key].deleteMany({}); } });
二、通用仓储(非单实体对应单仓储)的标准测试方式
1. 仓储自身的测试(集成测试)
针对MongoRepository的方法逻辑(如查询、更新正确性),用内存Mongo做集成测试,验证与数据库的真实交互:
describe('MongoRepository', () => { let repository: MongoRepository; let testModel: Model<TestEntity>; let mongod: MongoMemoryServer; let connection: Connection; beforeAll(async () => { mongod = await MongoMemoryServer.create(); const uri = mongod.getUri(); connection = (await mongoose.connect(uri)).connection; testModel = connection.model('TestEntity', TestEntitySchema); repository = new MongoRepository(); // 或通过Nest容器获取实例 }); afterAll(async () => { await connection.dropDatabase(); await connection.close(); await mongod.stop(); }); it('should return correct document when findOne is called', async () => { const testDoc = await testModel.create({ name: 'test' }); const result = await repository.findOne(testModel, { name: 'test' }); expect(result).toMatchObject({ name: 'test', _id: testDoc._id }); }); });
2. 依赖仓储的服务测试(单元测试)
针对业务服务(如ConversationsService),专注验证业务逻辑,Mock仓储的所有调用:
describe('ConversationsService', () => { let service: ConversationsService; let mockRepo: jest.Mocked<MongoRepository>; beforeEach(async () => { // 初始化Mock仓储 mockRepo = { findOneById: jest.fn(), updateOneById: jest.fn(), } as jest.Mocked<MongoRepository>; const module: TestingModule = await Test.createTestingModule({ providers: [ ConversationsService, { provide: MongoRepository, useValue: mockRepo }, { provide: ChannelsService, useValue: {} }, // Mock其他依赖 ], }).compile(); service = module.get<ConversationsService>(ConversationsService); }); it('should call repository findOneById when getting conversation', async () => { const mockResult = { _id: '123', title: 'Test' }; mockRepo.findOneById.mockResolvedValue(mockResult); const result = await service.getConversation('123'); expect(mockRepo.findOneById).toHaveBeenCalledWith(expect.any(Model), '123'); expect(result).toEqual(mockResult); }); });
三、常见问题排查
- 依赖遗漏:检查服务注入的所有提供者(仓储、其他服务、Model)是否都在测试模块中提供了Mock或真实实例
- 循环依赖处理:对于
forwardRef引入的模块,要么Mock其导出的核心服务,要么用overrideModule替换为测试专用模块 - 类型安全:用
jest.Mocked<T>包裹Mock对象,确保与原类的方法签名匹配
内容的提问来源于stack exchange,提问作者sant016
相关产品推荐
相关产品推荐

