如何用Jest测试Nest.js+Mongoose服务的模型构造函数?
问题描述
我用Jest给基于Nest.js和Mongoose开发的UsersService写单元测试,能正常mock find、exec这类模型静态方法,但不知道怎么处理new this.userModel(createUserDto)这种模型构造函数的场景。
我的Service代码:
import { Injectable } from '@nestjs/common'; import { CreateUserDto } from './dto/create-user.dto'; import { User, UserDocument } from './schemas/user.schema'; import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; @Injectable() export class UsersService { constructor(@InjectModel(User.name) private userModel: Model<UserDocument>) {} async create(createUserDto: CreateUserDto) { const user = new this.userModel(createUserDto); await user.save(); return user; } }
测试代码:
import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; import { getModelToken } from '@nestjs/mongoose'; import { User, UserDocument } from './schemas/user.schema'; import { CreateUserDto } from './dto/create-user.dto'; import { Model } from 'mongoose'; describe('UsersService', () => { let service: UsersService; let userModel: Model<UserDocument>; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ UsersService, { provide: getModelToken(User.name), useValue: Model<UserDocument> }, ], }).compile(); service = module.get<UsersService>(UsersService); userModel = module.get<Model<UserDocument>>(getModelToken(User.name)); }); it('should create a user', async () => { const createUserDto: CreateUserDto = { name: 'John', age: 25, }; const result = await service.create(createUserDto); expect(result).toEqual(createUserDto); }); });
运行测试时出现错误:
● UsersService › should create a user TypeError: Cannot read properties of undefined (reading 'tree') 10 | 11 | async create(createUserDto: CreateUserDto) { > 12 | const user = new this.userModel(createUserDto); | ^ 13 | await user.save(); 14 | return user; 15 | } at Model.Object.<anonymous>.Document.$__setSchema (../node_modules/mongoose/lib/document.js:3506:18) at Model.Document (../node_modules/mongoose/lib/document.js:89:10) at new Model (../node_modules/mongoose/lib/model.js:123:12) at UsersService.create (modules/users/users.service.ts:12:18) at Object.<anonymous> (modules/users/users.service.spec.ts:29:34)
解决方案
问题出在直接把Mongoose的Model类作为useValue传入,这会导致实例化时依赖Mongoose内部未初始化的Schema结构,从而抛出错误。正确的做法是mock模型的构造函数以及实例的save方法,具体步骤如下:
1. 构造Mock模型对象
创建包含模拟构造函数的对象,同时模拟实例的save方法,用Jest的jest.fn()实现:
const mockUserModel = jest.fn().mockImplementation((dto) => ({ ...dto, save: jest.fn().mockResolvedValue(dto) }));
2. 在测试模块中注入Mock模型
修改测试模块的providers配置,替换原有的useValue:
beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ UsersService, { provide: getModelToken(User.name), useValue: mockUserModel }, ], }).compile(); service = module.get<UsersService>(UsersService); });
3. 完善测试断言
在测试用例中验证构造函数和save方法的调用情况:
it('should create a user', async () => { const createUserDto: CreateUserDto = { name: 'John', age: 25, }; const result = await service.create(createUserDto); // 验证构造函数被调用且参数正确 expect(mockUserModel).toHaveBeenCalledWith(createUserDto); // 验证save方法被执行 expect(result.save).toHaveBeenCalled(); // 验证返回值符合预期 expect(result).toEqual(createUserDto); });
完整修改后的测试代码
import { Test, TestingModule } from '@nestjs/testing'; import { UsersService } from './users.service'; import { getModelToken } from '@nestjs/mongoose'; import { User, UserDocument } from './schemas/user.schema'; import { CreateUserDto } from './dto/create-user.dto'; describe('UsersService', () => { let service: UsersService; const mockUserModel = jest.fn().mockImplementation((dto) => ({ ...dto, save: jest.fn().mockResolvedValue(dto) })); beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ UsersService, { provide: getModelToken(User.name), useValue: mockUserModel }, ], }).compile(); service = module.get<UsersService>(UsersService); }); it('should create a user', async () => { const createUserDto: CreateUserDto = { name: 'John', age: 25, }; const result = await service.create(createUserDto); expect(mockUserModel).toHaveBeenCalledWith(createUserDto); expect(result.save).toHaveBeenCalled(); expect(result).toEqual(createUserDto); }); });
关键说明
- 不要直接使用Mongoose的
Model类作为mock值,它依赖内部Schema等初始化逻辑,测试环境中无法正常工作。 - 通过
jest.fn().mockImplementation模拟构造函数行为,让它返回带有save方法的实例对象,覆盖new this.userModel()和user.save()两个关键步骤。 - 测试时不仅要验证返回值,还要验证mock函数的调用情况,确保Service逻辑符合预期。
内容的提问来源于stack exchange,提问作者Paul Zaslavskij
相关产品推荐
相关产品推荐

