如何使用Jest测试定时器超时后触发的React组件重新渲染
问题根因
jest.useFakeTimers()仅模拟了setTimeout等定时器类宏任务,你用到的delay是返回Promise的异步封装:定时器回调被jest.runAllTimers()触发后,Promise的resolve回调属于微任务队列,不会被同步执行,此时await delay(1000)后的setFoo逻辑还未运行,就触发了后续断言,自然会出现断言失败、以及React状态更新未被act包裹的警告。
修复方案
在jest.runAllTimers()执行后,新增一行等待微任务队列清空的逻辑即可,你的测试用例已经声明为async函数,直接加await Promise.resolve()就能让所有 pending 状态的微任务执行完毕,等待React完成重渲染后再执行断言。
修改后的完整测试代码:
import { render } from '@testing-library/react'; import { useEffect, useState } from 'react'; import { delay } from './delay'; describe('useState', () => { it("should set rerender when setting state", async () => { jest.useFakeTimers(); let renderCount = 0; function MyComponent() { const [foo, setFoo] = useState("bar"); ++renderCount; useEffect(() => { (async function asyncEffect() { await delay(1000); setFoo("mew") })() }, []) return (<div data-testid="test">{foo}</div>); } const { getByTestId } = render(<MyComponent />) expect(getByTestId("test").textContent).toEqual("bar"); expect(renderCount).toEqual(1); jest.runAllTimers(); // 新增等待微任务执行完毕的逻辑 await Promise.resolve(); expect(getByTestId("test").textContent).toEqual("mew"); expect(renderCount).toEqual(2); }) afterEach(() => { jest.useRealTimers(); }) })
可选优化
你也可以直接用Testing Library提供的异步查询能力简化代码,把断言改为expect(await findByTestId('test')).toHaveTextContent('mew'),该方法会自动等待异步更新完成,不需要手动处理定时器和微任务的时序问题。
内容的提问来源于stack exchange,提问作者Archimedes Trajano
相关产品推荐
相关产品推荐

