You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 17:44:51