Sinon clock.tick无法推进setTimeout时间的测试问题
这个问题我之前也碰到过,核心是没处理好async函数的Promise和sinon假定时器的微任务执行顺序。咱们一步步来解决:
问题根因分析
你写的foo是async函数,调用它会立即返回一个Promise,但函数内部的await setTimeout逻辑是异步的。原来的测试里犯了两个关键错误:
- 没有等待
foo返回的Promise完成,导致在clock.tick后,Promise的resolve回调触发了,但await后面的f()还在微任务队列里没执行,这时候断言自然失败。 - 没必要tick 100000毫秒,60000毫秒就够触发那个setTimeout了。
修正后的测试代码
把测试函数改成async,并且等待foo的Promise完成,同时给测试加上时钟的前后置处理避免污染其他用例:
const sinon = require('sinon'); const expect = require('chai').expect; const { foo } = require('./foo'); describe('Module Foo ', function() { let clock; // 每个测试前初始化假时钟 beforeEach(function() { clock = sinon.useFakeTimers(); }); // 每个测试后恢复真实时钟 afterEach(function() { clock.restore(); }); it('calls function after 1 minute', async function() { const bar = sinon.stub(); const fooPromise = foo(bar); // 保存async函数返回的Promise // 初始状态下bar未被调用 expect(bar.called).to.be.false; // 快进60秒,触发setTimeout的resolve clock.tick(60000); // 等待foo内部的所有异步逻辑执行完毕 await fooPromise; // 此时bar已经被调用 expect(bar.called).to.be.true; }); });
另一种简化写法(不需要保存Promise)
如果不想额外保存fooPromise,也可以在clock.tick后等待一个空Promise,触发微任务队列执行:
it('calls function after 1 minute', async function() { const bar = sinon.stub(); foo(bar); expect(bar.called).to.be.false; clock.tick(60000); // 等待微任务队列处理完(也就是await后面的f()执行) await Promise.resolve(); expect(bar.called).to.be.true; });
为什么这样能解决问题?
当你调用clock.tick(60000)时,sinon会触发fake setTimeout的回调,resolve那个等待的Promise。但await Promise.resolve()之后的代码(也就是f())会被放到微任务队列里,Node.js的事件循环会先处理完当前宏任务,再处理微任务。所以我们需要用await来等待微任务队列执行完毕,才能确保bar已经被调用。
内容的提问来源于stack exchange,提问作者Moglum
相关产品推荐
相关产品推荐

