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

如何在Express路由中模拟Mongoose save()方法抛出的错误?

如何测试Mongoose save()抛出错误的路由场景(实现100%测试覆盖率)

嘿,我懂你现在的困扰——为了凑满istanbul的100%覆盖率,死活摸不准怎么模拟Mongoose save()抛出错误的场景对吧?刚好我之前也踩过这个坑,用Sinon就能轻松搞定,下面给你一步步拆解:

首先,我们需要用Sinon来Stub(模拟)Mongoose模型实例的save方法,让它在被调用时主动抛出错误,这样就能触发controller里的错误处理分支。这里我假设你用supertest来测试Express路由(如果还没装,直接npm install supertest --save-dev就行)。

完整测试用例代码(回调风格)

const sinon = require('sinon');
const chai = require('chai');
const expect = chai.expect;
const request = require('supertest');
const app = require('../routes'); // 引入你的路由文件
const Paste = require('../model');

describe('DELETE /pastes/:id', () => {
  it('should handle an error during the save in the endpoint', (done) => {
    // 1. 创建测试用的Paste数据并保存到数据库
    const pasteItem = new Paste({ message: 'Test 1', tags: ['integration', 'test'] });
    
    pasteItem.save()
      .then((paste) => {
        // 2. Stub住Paste原型上的save方法,让它调用回调时传入模拟错误
        const saveStub = sinon.stub(Paste.prototype, 'save').callsArgWith(0, new Error('Mock save failure'));

        // 3. 发送DELETE请求到目标路由
        request(app)
          .delete(`/pastes/${paste._id}`)
          .end((err, res) => {
            // 4. 断言响应符合预期
            expect(res.status).to.equal(400);
            expect(res.body.result).to.be.an('error');
            expect(res.body.result.message).to.equal('Mock save failure');

            // 5. 恢复Stub,避免影响其他测试
            saveStub.restore();
            done(); // 标记测试结束
          });
      })
      .catch((err) => {
        console.error('测试前置步骤出错:', err);
        done(err);
      });
  });
});

更简洁的Async/Await版本

如果你习惯用async/await,代码会更清爽,不用嵌套回调:

it('should handle an error during the save in the endpoint', async () => {
  // 1. 创建并保存测试数据
  const pasteItem = new Paste({ message: 'Test 1', tags: ['integration', 'test'] });
  const paste = await pasteItem.save();

  // 2. Stub save方法
  const saveStub = sinon.stub(Paste.prototype, 'save').callsArgWith(0, new Error('Mock save failure'));

  // 3. 发送请求并获取响应
  const res = await request(app).delete(`/pastes/${paste._id}`);

  // 4. 断言
  expect(res.status).to.equal(400);
  expect(res.body.result.message).to.equal('Mock save failure');

  // 5. 恢复Stub
  saveStub.restore();
});

关键步骤解释

  • Stub原型方法:所有Paste实例的save方法都继承自Paste.prototype,所以我们Stub原型上的方法,就能让所有实例的save调用都返回我们模拟的错误。
  • callsArgWith:这个Sinon方法用来指定回调函数的参数——这里我们让save的第一个回调参数(也就是err)传入一个自定义错误,刚好触发controller里的err ? res.status(400)...分支。
  • 恢复Stub:测试结束后一定要调用saveStub.restore(),把原型方法恢复原样,否则会影响其他测试用例的执行。
  • 清理测试数据:建议加个afterEach钩子,测试后清空数据库,避免测试数据残留影响后续测试:
    afterEach(async () => {
      await Paste.deleteMany({});
    });
    

这样一来,你之前没覆盖到的save错误分支就会被执行,istanbul的覆盖率就能达到100%啦!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:47:45