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

如何在Mocha中使用Sinon测试多回调异步函数?

异步回调+Sinon间谍测试的最佳实践

你的测试逻辑能正常运行,但确实有更规范、更简洁的实现方式——手动维护回调计数器不仅容易出错,还浪费了Sinon和测试框架本身提供的异步处理能力。下面是几个针对这类场景的最佳实践:

  • 直接利用函数返回的Promise,抛弃手动计数器
    你的函数f本身就返回Promise,完全不需要手动写done和cbCount来跟踪回调完成状态!可以直接返回这个Promise,在then块里用Sinon的断言方法验证间谍调用:

    it('test', () => {
      const cb1Spy = sinon.spy();
      const cb2Spy = sinon.spy();
    
      // 返回Promise让测试框架自动处理异步
      return f(cb1Spy, cb2Spy).then(() => {
        // Sinon提供的断言方法能给出清晰的错误提示
        sinon.assert.called(cb1Spy);
        sinon.assert.called(cb2Spy);
        // 也可以更精确地断言调用次数
        // sinon.assert.calledOnce(cb1Spy);
      });
    });
    
  • 用async/await让代码更易读
    现代测试框架(比如Mocha、Jest)都支持async/await语法,用它来处理异步测试会让逻辑更直观,完全避免回调嵌套:

    it('test', async () => {
      const cb1Spy = sinon.spy();
      const cb2Spy = sinon.spy();
    
      // 等待Promise完成
      await f(cb1Spy, cb2Spy);
      
      // 直接断言即可
      sinon.assert.calledOnce(cb1Spy);
      sinon.assert.calledOnce(cb2Spy);
    });
    
  • 注意你原测试里的隐藏问题
    顺便提一句:你原代码里的() => { cb1Spy; checkIfDone(); }其实没有调用间谍,只是引用了变量而已!正确的做法应该是直接把间谍传进f,或者在包装函数里调用cb1Spy(),不然你的间谍永远不会被标记为已调用。用上面的Promise/async方案就不会出现这种低级错误,因为我们直接把间谍作为回调传递给函数了。

  • 如果函数不返回Promise的兼容方案
    要是遇到某些特殊场景,异步函数没有返回Promise,也别手动维护计数器。可以用Sinon的断言结合轮询或者监听间谍调用状态:

    it('test', (done) => {
      const cb1Spy = sinon.spy();
      const cb2Spy = sinon.spy();
    
      f(cb1Spy, cb2Spy);
    
      // 轮询检查两个间谍是否都被调用
      const verify = () => {
        if (cb1Spy.called && cb2Spy.called) {
          sinon.assert.calledOnce(cb1Spy);
          sinon.assert.calledOnce(cb2Spy);
          done();
        } else {
          setTimeout(verify, 10);
        }
      };
      verify();
    });
    

    不过这种情况还是建议尽量让异步函数返回Promise,测试起来会顺畅很多。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:47:51