使用Jest 26.6.3时,setInterval内的Spy断言失效问题排查
问题分析与解决
这不是Jest的Bug,是你的操作存在两处问题:
核心原因
- Spy目标错误:你用
jest.mock("./MyClass")自动mock了整个类,此时MyClass的所有实例方法都是指向原型上的mock函数。你在实例myObject上调用jest.spyOn,无法正确捕获原型方法的调用(实例的foo方法只是原型方法的引用,spy替换没有覆盖到真实调用路径)。 - 不必要的异步标记:测试中
jest.advanceTimersByTime是同步操作,不需要添加async和done,这虽然不直接导致断言失败,但属于冗余代码。
修正后的测试代码
方法一:Spy类的原型方法
import { MyClass } from "./MyClass"; jest.mock("./MyClass"); jest.useFakeTimers(); describe("MyClass", () => { let myObject: MyClass = new MyClass(); beforeEach(() => { jest.resetAllMocks(); }); it("works", () => { const fooSpy = jest.spyOn(myObject, "foo"); myObject.foo(); expect(fooSpy).toHaveBeenCalledTimes(1); }); it("should work now", () => { // 改为监听类的原型方法,确保捕获所有实例调用 const fooSpy = jest.spyOn(MyClass.prototype, "foo"); const callback = () => { myObject.foo(); }; const intervalId = setInterval(callback, 1000); jest.advanceTimersByTime(1000); // 清理定时器,避免影响后续测试 clearInterval(intervalId); expect(fooSpy).toHaveBeenCalledTimes(1); }); });
方法二:直接使用原型的Mock函数断言(更简洁)
因为已经通过jest.mock自动mock了类,原型上的foo本身就是Mock函数,可以直接用它来断言,无需额外Spy:
import { MyClass } from "./MyClass"; jest.mock("./MyClass"); jest.useFakeTimers(); describe("MyClass", () => { let myObject: MyClass = new MyClass(); beforeEach(() => { jest.resetAllMocks(); }); it("works", () => { const fooSpy = jest.spyOn(myObject, "foo"); myObject.foo(); expect(fooSpy).toHaveBeenCalledTimes(1); }); it("should work now", () => { const callback = () => { myObject.foo(); }; const intervalId = setInterval(callback, 1000); jest.advanceTimersByTime(1000); clearInterval(intervalId); // 直接断言原型上的mock方法调用次数 expect(MyClass.prototype.foo).toHaveBeenCalledTimes(1); }); });
额外建议
使用fake timers后,记得在测试结束清理定时器(如clearInterval或jest.clearAllTimers()),避免定时器残留影响其他测试用例的执行。
内容的提问来源于stack exchange,提问作者user6106573
相关产品推荐
相关产品推荐

