如何在NestJS中对依赖两个模型的功能进行单元测试?
为依赖其他MongoDB仓库的NestJS仓库编写单元测试
问题场景
你有两个NestJS Mongoose仓库:UsersRepository依赖OrdersRepository,调用其find方法实现getOrders功能。编写单元测试时,添加第二个仓库/模型后出现connection.close()为undefined的错误。
相关代码
UsersRepository与UserModule
@Injectable() export class UsersRepository extends EntityRepository<UserDocument> { constructor( @InjectModel(User.name) userModel: Model<UserDocument>, protected readonly orderRepository: OrdersRepository ) { super(userModel) } async getOrders(id: string) { const orders = await this.orderRepository.find({ userId: id }); return orders; } } @Module({ imports: [ MongooseModule.forFeature( [{ name: User.name, schema: UserSchema }], 'TEST-V2', ), OrderModule, ], controllers: [UserController], providers: [UserService, UserRepository], exports: [UserService, UserRepository], }) export class UserModule {}
OrdersRepository与OrderModule
@Injectable() export class OrdersRepository extends EntityRepository<OrderDocument> { constructor(@InjectModel(Order.name) orderModel: Model<OrderDocument>) { super(orderModel) } } @Module({ imports: [ MongooseModule.forFeature( [{ name: Order.name, schema: OrderSchema }], 'TEST-V2', ), ], providers: [OrderService, OrderRepository], exports: [OrderService, OrderRepository], }) export class OrderModule {}
你的测试代码(存在问题)
describe('UsersRepository', () => { let userModel: UserModel; let userRepository: UserRepository; let orderRepository: OrdersRepository; let orderModel: OrderModel; let connection: Connection; beforeEach(async () => { const moduleRef = await Test.createTestingModule({ imports: [ ConfigModule.forRoot({ validationSchema: envVarValidationSchema, isGlobal: true, cache: true, envFilePath: `.env`, validationOptions: { allowUnknown: true, abortEarly: false, }, load: [configuration], }), MongooseModule.forRootAsync({ connectionName: 'TEST-V2', imports: [ConfigModule], inject: [ConfigService], useFactory: async (configService: ConfigService) => ({ uri: configService.get('db.mongo.testv2'), }), }), ], providers: [ UsersRepository, OrdersRepository, { provide: getModelToken(Order.name), useClass: OrderModel, }, { provide: getModelToken(User.name), useClass: UserModel, }, ], }).compile(); // ... 获取实例 connection = await moduleRef.get(getConnectionToken('TEST-V2')); jest.clearAllMocks(); }) afterEach(async () => { await connection.close(); }); })
错误原因
- 单元测试思路错误:你当前写法偏向集成测试,但手动用
useClass绑定模型,导致Mongoose连接未正确关联到模型实例,最终connection获取失败为undefined。 - 依赖注入混乱:手动注册仓库和模型的方式,破坏了NestJS模块的自动依赖解析逻辑,导致连接实例无法正常注入。
解决方案
方案一:模拟OrdersRepository(单元测试推荐)
单元测试核心是隔离依赖,无需连接真实数据库,直接模拟OrdersRepository的方法即可:
describe('UsersRepository', () => { let userRepository: UsersRepository; // 模拟OrdersRepository实例 let mockOrderRepository: Partial<OrdersRepository>; beforeEach(async () => { // 创建模拟对象,预设find方法返回值 mockOrderRepository = { find: jest.fn().mockResolvedValue([{ _id: 'order1', userId: 'user1' }]) }; const moduleRef = await Test.createTestingModule({ providers: [ UsersRepository, // 注入模拟的OrdersRepository { provide: OrdersRepository, useValue: mockOrderRepository }, // 模拟UserModel(测试getOrders无需实际操作User模型) { provide: getModelToken(User.name), useValue: jest.fn() } ] }).compile(); userRepository = moduleRef.get(UsersRepository); }); it('should return orders via OrderRepository', async () => { const userId = 'user1'; const result = await userRepository.getOrders(userId); // 验证mock方法被正确调用 expect(mockOrderRepository.find).toHaveBeenCalledWith({ userId }); // 验证返回值符合预期 expect(result).toEqual([{ _id: 'order1', userId: 'user1' }]); }); });
方案二:使用测试数据库(集成测试)
如果需要测试真实数据库交互,直接导入模块而非手动注册模型,让NestJS自动处理连接:
describe('UsersRepository (Integration)', () => { let userRepository: UsersRepository; let connection: Connection; beforeEach(async () => { const moduleRef = await Test.createTestingModule({ imports: [ ConfigModule.forRoot({ isGlobal: true, envFilePath: '.env.test' // 使用单独的测试环境变量文件 }), // 直接导入UserModule,自动关联OrderModule和Mongoose连接 UserModule ] }).compile(); userRepository = moduleRef.get(UsersRepository); connection = moduleRef.get(getConnectionToken('TEST-V2')); }); afterEach(async () => { // 清空测试数据,避免影响后续测试 await connection.collection('users').deleteMany({}); await connection.collection('orders').deleteMany({}); await connection.close(); }); it('should fetch real orders for a user', async () => { // 插入测试数据 await connection.collection('orders').insertOne({ userId: 'user2', product: 'test-product' }); const result = await userRepository.getOrders('user2'); expect(result.length).toBe(1); expect(result[0].userId).toBe('user2'); }); });
关键注意点
- 单元测试:聚焦
UsersRepository自身逻辑,模拟所有外部依赖,保证测试速度和稳定性,无需真实数据库。 - 集成测试:测试模块间真实交互和数据库操作,使用独立的测试数据库,避免污染生产环境数据。
内容的提问来源于stack exchange,提问作者Nathan Bernardo
相关产品推荐
相关产品推荐

