如何在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
相关产品推荐
相关产品推荐

