如何在 React 测试中等待 useEffect 完成更新并触发重渲染

3次阅读

如何在 React 测试中等待 useEffect 完成更新并触发重渲染

本文详解如何在单元测试中正确等待 useeffect 中的状态更新完成,避免因异步渲染导致断言失败,涵盖 `act()` 封装、`waitfor` 配合 rtl 的两种专业方案。

react 测试中,useEffect 内部调用 setState(如 setCtr(1))会触发异步重渲染——该更新被加入 React 的调度队列,并不会立即同步执行。因此,直接在 Reactdom.render() 后立刻读取 DOM(如 el.innerHTML),往往捕获的是初始渲染结果(0),而非 useEffect 更新后的值(1)。这是初学者常遇的“断言提前执行”问题。

✅ 正确解法一:使用 act() 包裹渲染(原生 ReactDOM 测试)

act() 是 React 提供的官方工具,用于确保所有状态更新、生命周期和 effects 在断言前完全同步完成。它模拟浏览器事件循环行为,强制 React 刷洗所有待处理的更新:

import { render } from "react-dom"; import { act } from "react-dom/test-utils"; import App from "./App";  it("should render 1", async () => {   const el = document.createElement("div");    // ✅ 关键:用 act 包裹 render,确保 useEffect 执行完毕且 DOM 已更新   await act(async () => {     render(, el);   });    expect(el.innerHTML).toBe("1"); // ✅ 现在通过! });

⚠️ 注意:act() 必须是 async/await 形式(或返回 Promise),因为 useEffect 中的 setState 触发的是异步更新。旧版 act(() => { … }) 同步写法在此场景下无法保证 effect 完成。

✅ 正确解法二:采用 React Testing Library(推荐)

@testing-library/react(RTL)封装了最佳实践,默认使用 act,并提供语义化异步工具(如 waitFor),更贴近真实用户交互逻辑:

// __tests__/App.test.tsx import { render, screen, waitFor } from "@testing-library/react"; import App from "../App";  test("renders 1 after useEffect", async () => {   render();    // ✅ 自动等待 DOM 出现文本 "1"(内部已用 act 保障)   await waitFor(() => {     expect(screen.getByText("1")).toBeInTheDocument();   }); });

同时需确保组件返回可测内容(如包裹

或语义化标签):

// App.tsx(修复原始无标签问题) export default function App() {   const [ctr, setCtr] = useState(0);   useEffect(() => {     setCtr(1);   }, []);   return 
{ctr}
; // ✅ 返回有效 DOM 节点 }

❌ 常见错误与避坑指南

  • 不要用 setTimeout / for 循环轮询:JavaScript 单线程 + 事件循环机制下,同步代码(包括长循环)会阻塞 microtask 队列,反而延迟 useEffect 执行,属于反模式。
  • 不要忽略 act 的异步性:act(() => render(…)) 不足以等待 effect;必须 await act(async () => {…})。
  • 避免直接操作 innerHTML 断言:RTL 的 screen.getByText() 等查询器自带重试逻辑,更健壮;若坚持 DOM 操作,请配合 waitFor。

总结

方案 适用场景 关键要点
act() + ReactDOM.render 轻量级、已有 DOM 测试基础 必须 await act(async () => {…})
React Testing Library 推荐生产级测试 使用 render + waitFor,语义清晰、内置 act

无论选择哪种方式,核心原则不变:React 的状态更新与 DOM 渲染是异步批处理过程,测试必须显式等待其完成。遵循 act 或 RTL 的约定,即可稳定捕获 useEffect 后的真实 ui 状态。

text=ZqhQzanResources