Node.js中使用Sinon Stub返回Promise的函数失效问题
为什么Sinon Stub返回Promise的函数没生效?
我懂你遇到的问题了——你明明用Sinon创建了返回rejected Promise的stub,但toTest函数还是老老实实地调用了原始的toStub,导致测试走了then分支而不是预期的catch分支。这背后的问题其实是模块导出的变量引用问题。
问题根源
你在测试文件里重新赋值了本地变量toStub:
toStub = sinon.stub().rejects('foofoo');
但index.js里的toTest函数,调用的是模块内部的toStub变量,不是你测试文件里的那个。Node.js模块导出的是值的副本,不是引用,所以你修改测试里的变量,根本影响不到模块内部的函数调用。
解决方案:两种可行的修复方式
方法1:让toTest引用模块导出的toStub
修改你的index.js,让toTest调用module.exports.toStub()而不是内部的toStub:
const toStub = () => { return Promise.resolve('foo'); }; const toTest = () => { // 改为调用模块导出的toStub,这样测试时可以替换它 return module.exports.toStub() .then((r) => { console.log(`THEN: ${r}`); return 42; }) .catch((e) => { console.log(`CATCH: ${e}`); throw 21; }); }; module.exports = { toStub, toTest };
然后在测试文件里,直接stub模块导出的toStub方法,并且要注意异步测试的正确写法(不要提前调用done()):
let chai = require('chai') , should = chai.should(); let sinon = require('sinon'); // 导入整个模块,而不是解构单个函数 let indexModule = require('../src/index'); describe('test the toTest function', () => { it('should be rejected', (done) => { // Stub模块上的toStub方法 const stub = sinon.stub(indexModule, 'toStub').rejects('foofoo'); indexModule.toTest() .then(r => { // 如果走到这里,说明测试失败 done.fail('Expected promise to be rejected'); }) .catch(e => { console.log(`CATCH_2 ${e}`); // 验证捕获的错误是否符合预期 e.should.equal(21); // 恢复stub,避免影响其他测试用例 stub.restore(); done(); }) // 捕获测试过程中的意外错误 .catch(done); }); });
方法2:用依赖注入(更推荐的测试友好写法)
这种方式不需要依赖模块内部的引用关系,直接给toTest传入要使用的函数:
修改index.js,让toTest接受一个可选参数,默认用原始的toStub:
const toStub = () => { return Promise.resolve('foo'); }; // 允许传入自定义的函数,默认使用原始toStub const toTest = (stubFn = toStub) => { return stubFn() .then((r) => { console.log(`THEN: ${r}`); return 42; }) .catch((e) => { console.log(`CATCH: ${e}`); throw 21; }); }; module.exports = { toStub, toTest };
测试文件就可以直接传入stub,写法更简洁,也不需要关心模块内部结构:
let chai = require('chai') , should = chai.should(); let sinon = require('sinon'); let { toTest } = require('../src/index'); describe('test the toTest function', () => { // 用async/await替代done,代码更清晰 it('should be rejected', async () => { const stubbedToStub = sinon.stub().rejects('foofoo'); try { await toTest(stubbedToStub); // 如果执行到这里,说明Promise没有被reject,测试失败 chai.expect.fail('Expected promise to reject'); } catch (e) { console.log(`CATCH_2 ${e}`); e.should.equal(21); } }); });
额外注意点
- 原来的测试代码里直接调用
done(),这会导致测试在Promise执行完成前就结束,所以一定要把done()放在异步回调里,或者用async/await写法。 - 用完stub后记得调用
restore(),避免影响其他测试用例(方法1需要,方法2因为是传入的独立stub,不需要)。
内容的提问来源于stack exchange,提问作者inv_det
相关产品推荐
相关产品推荐

