应使用 fake timers 或手动 mock requestAnimationFrame 并显式触发回调,聚焦断言动画状态(如 style、进度值、回调参数)而非视觉效果,并覆盖 cancelAnimationFrame 等生命周期逻辑。

测试含 requestAnimationFrame(简称 rAF)的前端动画代码,核心是**控制时间流、捕获帧调度、避免真实渲染开销**。不能依赖真实浏览器帧率(太慢、不可控),也不能直接 mock rAF 后忽略其异步特性(会漏掉竞态、状态更新时机等关键逻辑)。
用 fake timers 模拟 rAF 调度
Jest、Vitest 等现代测试框架支持高级定时器模拟。以 Vitest 为例(Jest 类似):
- 启用
fakeTimers: { enableGlobally: true, toFake: ['setTimeout', 'setInterval', 'requestAnimationFrame'] } - 调用
vi.runOnlyPendingTimers()或await vi.runAllTicks()触发已注册的 rAF 回调(rAF 在 fake timers 中被映射为 microtask 或 immediate 执行) - 注意:rAF 在 fake 模式下不会等待 16ms,而是“立即”执行回调——这正是你想要的:快、可预测、可断言
手动 mock rAF 并显式触发
适合轻量测试或需精细控制帧序的场景:
- 在测试前保存原函数:
const originalRAF = window.requestAnimationFrame; - mock 成一个队列:
const rafQueue = [];,window.requestAnimationFrame = cb => { rafQueue.push(cb); return 1; }; - 写个辅助函数:
function flushRAF() { const cbs = [...rafQueue]; rafQueue.length = 0; cbs.forEach(cb => cb()); } - 在断言前调用
flushRAF(),确保所有待执行帧回调完成
断言动画状态而非视觉效果
不要测“元素是否真的移动了 10px”,而要测:
立即学习“前端免费学习笔记(深入)”;
- 目标元素的
style.transform、style.opacity等是否按预期更新 - 动画函数内部状态(如当前进度值、是否暂停/完成)是否正确
- 是否正确调用了依赖函数(如 onProgress 回调)并传入合理参数
- 多次调用
flushRAF()后,状态是否逐步演进(例如从 0 → 0.3 → 0.6 → 1.0)
处理 cancelAnimationFrame 和生命周期
真实动画常配合清理逻辑(如组件卸载时 cancel):
- mock
cancelAnimationFrame,记录被取消的 id,验证是否在正确时机调用 - 模拟组件 unmount:先触发清理逻辑,再 flush rAF,确认无残留回调执行
- 测试重复启动动画时,旧帧是否被取消(检查 cancel 调用次数和参数)
不复杂但容易忽略:rAF 的执行时机依赖于浏览器事件循环,测试中必须显式推进它;只要控制好调度、聚焦状态断言、覆盖取消路径,就能可靠验证动画逻辑。


















