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

NestJS测试Mongoose服务报错:无法监视save属性

解决NestJS测试中Cannot spy the save property的问题

咱们来拆解下你遇到的两个核心问题,这俩加起来导致了这个报错:

1. Spy的时机完全搞反了

你现在的代码是先调用了service.authLogging(),之后才去创建jest.spyOn(mockLoggingAuthModel, 'save')——这时候save早就执行完了,spy根本没机会捕捉到调用行为,而且更关键的是,你spy的对象也不对。

2. Mock模型的写法不符合NestJS中Mongoose Model的使用逻辑

在你的LoggingService里,this.loggingAuthModel是一个构造函数(Mongoose的Model类),你通过new this.loggingAuthModel(authLog)创建实例,然后调用实例上的save()方法。但你现在的MockLoggingAuthModel是一个类,你直接注入了这个类本身,然后试图spy这个类的save方法——但save是实例方法,不是静态方法,所以spy不到,自然会提示undefined。

修复方案

咱们一步步来改:

第一步:调整Mock模型,模拟Mongoose Model的构造行为

把你的MockLoggingAuthModel改成用jest函数模拟,让它的实例能返回带有mock save方法的对象:

// 替换原来的MockLoggingAuthModel和MockLoggingEventModel
const MockLoggingAuthModel = jest.fn(() => ({
  save: jest.fn().mockResolvedValue(null),
}));

const MockLoggingEventModel = jest.fn(() => ({
  save: jest.fn().mockResolvedValue(null),
}));

这样,当LoggingService里执行new this.loggingAuthModel(authLog)时,会返回一个带有save mock函数的对象。

第二步:修正测试中的断言逻辑

在测试authLogging的用例里,我们可以直接通过jest mock函数的实例记录来断言save被调用:

it('authLogging should call save on auth log model', async () => {
  const reqId = 'mock-request-id';
  const mockApiKey = 'mock-api-key';
  const mockStatusCode = 200;
  const mockInternalMessage = 'mock-message';

  // 先调用待测试的方法
  await service.authLogging(request, reqId, mockApiKey, mockStatusCode, mockInternalMessage);

  // 获取mock构造函数创建的实例
  const createdAuthLogInstance = MockLoggingAuthModel.mock.results[0].value;
  // 断言save方法被调用
  expect(createdAuthLogInstance.save).toHaveBeenCalled();
});

备选方案:用类Mock并spy原型方法

如果你还是想用类的方式写Mock,那可以直接spy类原型上的save方法:

class MockLoggingAuthModel {
  constructor() {}
  async save(): Promise<void> {}
}

// 在beforeEach里提前spy原型方法
beforeEach(async () => {
  request = new JestRequest();
  // 提前spy原型上的save方法,确保在service调用前生效
  jest.spyOn(MockLoggingAuthModel.prototype, 'save').mockResolvedValue();
  
  const module: TestingModule = await Test.createTestingModule({
    providers: [
      LoggingService,
      { provide: getModelToken(LOGGING_AUTH_MODEL), useValue: MockLoggingAuthModel },
      { provide: getModelToken(LOGGING_EVENT_MODEL), useValue: MockLoggingEventModel },
    ],
  }).compile();

  service = module.get(LoggingService);
});

it('authLogging should call save on auth log model', async () => {
  const reqId = 'mock-request-id';
  const mockApiKey = 'mock-api-key';
  const mockStatusCode = 200;
  const mockInternalMessage = 'mock-message';

  await service.authLogging(request, reqId, mockApiKey, mockStatusCode, mockInternalMessage);

  // 断言原型上的save方法被调用
  expect(MockLoggingAuthModel.prototype.save).toHaveBeenCalled();
});

关键要点总结

  • Spy必须在被测试方法调用前创建:不然根本捕捉不到调用行为
  • Mongoose Model是构造函数:注入的mock需要能被new调用,并且返回带有实例方法的对象
  • 区分实例方法和静态方法:save是实例方法,不能直接spy类本身的save属性

内容的提问来源于stack exchange,提问作者RemeJuan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 17:03:11