测试带setTimeout的异步逻辑需用假定时器(如jest.useFakeTimers()或vi.useFakeTimers())控制时间流逝,避免真实等待;通过advanceTimersByTime或runAllTimers推进时间,配合beforeEach/afterEach清理,确保测试高效可靠。

测试带有 setTimeout 的异步逻辑,核心是**控制时间流逝而不真实等待**,避免测试变慢、不可靠。JavaScript 测试框架(如 Jest、Vitest)都提供了模拟定时器的能力,关键在于正确启用和管理它们。
用 Jest 模拟定时器(推荐)
Jest 内置了对定时器的模拟支持,无需额外库。需在测试前调用 jest.useFakeTimers(),并在测试后清理(或使用 jest.runOnlyPendingTimers() 等方法推进时间):
-
启用假定时器:在
beforeEach中调用jest.useFakeTimers() -
触发待执行的定时器:用
jest.runAllTimers()(执行所有已设但未触发的setTimeout),或jest.advanceTimersByTime(ms)(快进指定毫秒) -
清理还原:在
afterEach中调用jest.useRealTimers(),防止影响其他测试
示例:
test('should call callback after 100ms', () => {
jest.useFakeTimers();
const callback = jest.fn();
setTimeout(callback, 100);
expect(callback).not.toHaveBeenCalled();
jest.advanceTimersByTime(100); // 快进 100ms
expect(callback).toHaveBeenCalledTimes(1);
jest.useRealTimers();
});
用 Vitest 模拟定时器(现代替代方案)
Vitest 同样支持假定时器,API 更简洁:
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 用
vi.useFakeTimers()启用 - 用
vi.runAllTimers()或vi.advanceTimersByTime(ms)推进 - 用
vi.useRealTimers()还原(或直接在describe中用vi.mock隔离)
注意:Vitest 默认不自动清理,建议配合 beforeEach/afterEach 使用,或利用 vi.hoisted 统一管理。
避免常见陷阱
真实时间等待(如 await new Promise(r => setTimeout(r, 100)))会让测试变慢且不稳定,应避免:
- 不要在测试中用
await等待真实setTimeout—— 这不是“测试异步”,而是“拖慢测试” - 确保被测函数里的
setTimeout没被意外 mock 成其他行为(比如被全局 patch 错误) - 多个嵌套定时器时,用
advanceTimersByTime比runAllTimers更可控,避免跳过中间状态
需要验证“未触发”场景怎么办?
比如测试防抖函数在 200ms 内重复调用只执行最后一次:
- 调用函数多次
- 用
advanceTimersByTime(199)—— 此时不应执行 - 再
advanceTimersByTime(1)—— 触发最终执行 - 断言执行次数和参数是否符合预期
这种精确控制只有假定时器能可靠实现。

















