单元测试中模拟 requestIdleCallback 的核心是替换全局 API 并可控触发回调,需支持 IdleDeadline、timeout 和取消,覆盖分片、中断、超时、取消及降级逻辑,避免依赖真实空闲时机。

在单元测试中模拟 requestIdleCallback,核心是**替换全局 API 并可控触发回调**,同时确保能验证任务是否按空闲逻辑正确分片、中断与续传。它不是“等浏览器真空闲”,而是模拟其行为契约:回调在空闲时执行、带 IdleDeadline、支持 timeout 和取消。
用 jest.mock 模拟 requestIdleCallback 全局行为
在测试环境(如 Jest)中,直接覆盖 window.requestIdleCallback 和 window.cancelIdleCallback:
- 返回一个可记录调用参数的 mock id(如数字计数器)
- 把回调存起来,暴露
triggerIdleCallback()方法手动触发 - 模拟
IdleDeadline:提供timeRemaining()(可设为固定值或递减)和didTimeout - 必须实现
timeout逻辑:调用后若未手动触发,到时间自动执行并设didTimeout = true
测试关键路径:分片、中断、超时、取消
针对典型长任务拆分逻辑(如批量渲染、数据索引),写四类断言:
- 分片执行:检查首次调用后只处理前 N 条(如 30 条),DOM 节点数符合预期
-
主动中断:在回调中检查
deadline.timeRemaining() ,确认下一批被推迟而非跳过 -
超时兜底:mock timeout 为 50ms,不手动 trigger,验证 50ms 后回调仍被执行(
didTimeout === true) -
用户中断:调用
cancelIdleCallback(id)后再 trigger,确认回调未执行
降级逻辑也要覆盖(兼容 Safari 等旧环境)
如果代码里写了 if ('requestIdleCallback' in window) {...} else { setTimeout(...) },测试需双路径覆盖:
- 删除
window.requestIdleCallback属性,验证回退到setTimeout,且批大小、延迟行为一致 - 注意:setTimeout 版本无法模拟
timeRemaining(),此时应靠固定 chunkSize + 计时器控制节奏,测试重点转为“是否避免阻塞主线程”(例如用jest.advanceTimersByTime(1)验证每批后主线程释放)
避免常见测试陷阱
模拟不是越像真环境越好,关键是验证调度逻辑本身:
- 别 mock
performance.now()来“精确计时”——测试关注的是行为分支,不是毫秒级耗时 - 不要在测试里 await 真 idle 回调——它不可控;全部改为主动
triggerIdleCallback() - 状态变量(如当前索引、中间结果)必须在闭包或对象中持久化,测试要断言这些变量在多次 trigger 后正确延续


















