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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:13:02