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

如何解决Sinon Stub setInterval调用的异步函数时的测试失败问题?

解决Mocha测试中setInterval触发异步函数的断言时机问题

问题场景

需要测试包含私有异步函数Bar的Foo类:Bar由setInterval定时触发,内部调用sleep异步函数,执行流程为A() → 等待sleep → B()。当前测试中,断言B()被调用时,实际B()尚未执行,导致测试失败——A()的输出先出现,测试断言失败后B()才执行。

待测试的Foo类代码

export class Foo
{
    constructor ()
    {
        setInterval(async () => await this.Bar(), 1000);
    }

    private async Bar()
    {
        this.A();
        await this.sleep(5000);
        this.B();
    }

    private A()
    {
        console.log("A");
    }

    private B()
    {
        console.log("B");
    }

    public async sleep(ms: number)
    {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
}

原测试代码(失败)

it.only("test", () =>
{
    let cut = new Foo();

    let ASpy = sinon.spy(cut, 'A');
    let BSpy = sinon.spy(cut, 'B');       
    sinon.stub(cut, 'sleep').resolves();
    
    fakeClock.tick(1000);

    ASpy.should.be.calledOnce;
    BSpy.should.be.calledOnce;

    ASpy.restore();
    BSpy.restore();
});

问题原因

测试函数为同步执行,fakeClock.tick(1000)触发setInterval回调后,Bar中的await sleep()(已被stub为立即resolve)会将this.B()的执行放入微任务队列,而同步断言代码会在微任务执行前运行,此时B()还未被调用,导致断言失败。

解决方案

1. 将测试改为异步函数,等待微任务执行

修改测试函数为异步函数,在fakeClock.tick()后等待微任务队列清空,确保B()执行完毕后再进行断言:

it.only("test", async () => {
    let cut = new Foo();

    // TypeScript中私有方法需通过类型断言绕过检查
    let ASpy = sinon.spy(cut as any, 'A');
    let BSpy = sinon.spy(cut as any, 'B');       
    sinon.stub(cut, 'sleep').resolves();
    
    fakeClock.tick(1000);
    // 等待微任务队列执行,确保this.B()被调用
    await Promise.resolve();

    ASpy.should.be.calledOnce;
    BSpy.should.be.calledOnce;

    ASpy.restore();
    BSpy.restore();
});

2. 确保Fake Clock正确初始化

需在测试前后正确初始化和销毁Sinon的Fake Timer,覆盖setInterval和setTimeout:

let fakeClock;

beforeEach(() => {
    fakeClock = sinon.useFakeTimers();
});

afterEach(() => {
    fakeClock.restore();
});

额外优化(可选)

TypeScript中直接Spy私有方法会有类型警告,若想避免可考虑:

  • 将A()、B()改为protected方法,测试时继承Foo类进行Spy
  • 重构代码,将A()、B()的逻辑抽离为独立的可测试单元,通过依赖注入引入Foo类

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 18:05:34