如何使用Jest为自定义requestAnimationFrame(RAF)工具编写单元测试
测试实现方案
核心思路
你需要先Mock浏览器的requestAnimationFrame和cancelAnimationFrameAPI,把原本由浏览器调度的异步逻辑改成可手动控制的同步逻辑,这样就能精确控制每一帧的执行时机,验证回调触发的时机是否符合预期。同时因为你的代码里有模块级的全局变量i和idMap,每次测试前需要重置模块状态,避免不同测试用例互相干扰。
具体实现步骤
1. 编写Mock函数
首先实现RAF的模拟逻辑,把所有待执行的RAF回调存在队列里,提供手动执行指定帧数的方法:
// 放在测试文件顶部的mock逻辑 let rafQueue: Array<() => void> = [] let rafIdCounter = 0 beforeEach(() => { // 每次测试前重置队列和id计数 rafQueue = [] rafIdCounter = 0 // 重置模块,清空i和idMap的状态 jest.resetModules() // Mock requestAnimationFrame global.requestAnimationFrame = jest.fn((cb) => { const id = rafIdCounter++ rafQueue.push(cb) return id }) // Mock cancelAnimationFrame global.cancelAnimationFrame = jest.fn((id: number) => { rafQueue = rafQueue.filter((_, index) => index !== id) }) }) // 手动执行指定帧数的工具函数 function runRAFFrames(count: number) { for (let i = 0; i < count; i++) { // 每次执行前拷贝队列,避免执行过程中新增的回调影响当前帧 const currentQueue = [...rafQueue] rafQueue = [] currentQueue.forEach(cb => cb()) } }
2. 编写测试用例
测试场景1:默认1帧延迟下回调正常触发
test('createRAF 延迟1帧时回调正常触发', () => { const { createRAF } = require('./你的模块路径') const mockCallback = jest.fn() createRAF(mockCallback) // 还没执行帧,回调不触发 expect(mockCallback).not.toHaveBeenCalled() // 执行1帧 runRAFFrames(1) // 回调触发1次 expect(mockCallback).toHaveBeenCalledTimes(1) })
测试场景2:自定义延迟帧数下回调触发时机正确
test('createRAF 自定义延迟3帧时回调在第3帧后触发', () => { const { createRAF } = require('./你的模块路径') const mockCallback = jest.fn() createRAF(mockCallback, 3) // 执行2帧 runRAFFrames(2) expect(mockCallback).not.toHaveBeenCalled() // 执行第3帧 runRAFFrames(1) expect(mockCallback).toHaveBeenCalledTimes(1) })
测试场景3:cancelRAF 可以正常取消未执行的回调
test('cancelRAF 取消后回调不会触发', () => { const { createRAF, cancelRAF } = require('./你的模块路径') const mockCallback = jest.fn() const rafId = createRAF(mockCallback, 2) cancelRAF(rafId) // 执行2帧 runRAFFrames(2) expect(mockCallback).not.toHaveBeenCalled() expect(global.cancelAnimationFrame).toHaveBeenCalled() })
测试场景4:多个RAF实例并行执行互不干扰
test('多个createRAF实例并行时互不影响', () => { const { createRAF } = require('./你的模块路径') const cb1 = jest.fn() const cb2 = jest.fn() createRAF(cb1, 1) createRAF(cb2, 3) runRAFFrames(1) expect(cb1).toHaveBeenCalledTimes(1) expect(cb2).not.toHaveBeenCalled() runRAFFrames(2) expect(cb2).toHaveBeenCalledTimes(1) })
额外注意点
如果你的测试环境是jsdom,默认已经有requestAnimationFrame的实现,但默认是用微队列模拟的,无法精确控制帧数,所以必须手动Mock覆盖原生的实现才能达到逐帧验证的效果。
内容的提问来源于stack exchange,提问作者GuangWu
相关产品推荐
相关产品推荐

