如何让React测试等待useEffect触发的更新完成后执行断言?
React组件测试问题:等待useEffect触发的更新完成
组件代码
function App() { let [ctr, setCtr] = useState(0); useEffect(() => { setCtr(1); }, []); //<-------------------------在这里加断点,目前只会触发一次 return ctr; }
初始测试代码(执行失败)
// 调试这个测试用例 it("应该渲染1", () => { const el = document.createElement("div"); ReactDOM.render(<App />, el); expect(el.innerHTML).toBe("1"); // 这里执行失败! });
问题原因
测试断言expect(...)在useEffect触发的组件更新完成前就执行了,此时el.innerHTML还是初始值0,导致断言失败。
尝试过的无效方案
试过添加长循环试图等待更新完成,但JavaScript是单线程模型,同步循环会一直占用执行栈,不会让出时间给useEffect对应的微任务执行,循环结束后断言仍会立即执行,问题无法解决:
it("应该渲染1", () => { const el = document.createElement("div"); ReactDOM.render(<App />, el); for (let i = 0; i < 2000; i++) { // 加了长循环想让useEffect的更新有机会完成 // ... } expect(el.innerHTML).toBe("1"); // 还是失败! });
可行解决方案
方案1:使用React测试工具的act函数配合异步等待
React提供的act函数可以确保所有与组件相关的更新都完成后再执行断言,结合async/await处理异步更新:
import { act } from 'react-dom/test-utils'; it("应该渲染1", async () => { const el = document.createElement("div"); await act(async () => { ReactDOM.render(<App />, el); }); expect(el.innerHTML).toBe("1"); });
方案2:利用setTimeout等待微任务队列清空
useEffect的回调会在组件挂载后的微任务阶段执行,我们可以通过setTimeout(宏任务)等待微任务完成,再执行断言:
it("应该渲染1", (done) => { const el = document.createElement("div"); ReactDOM.render(<App />, el); setTimeout(() => { expect(el.innerHTML).toBe("1"); done(); // 通知测试框架异步测试完成 }, 0); });
方案3:使用React Testing Library的waitFor(推荐)
如果项目中使用React Testing Library,waitFor可以自动等待断言条件满足,写法更简洁:
import { render, waitFor } from '@testing-library/react'; it("应该渲染1", async () => { const { container } = render(<App />); await waitFor(() => { expect(container.innerHTML).toBe("1"); }); });
以上方案都能确保useEffect触发的更新完成后再执行断言,此时调试断点会触发两次(第一次初始渲染,第二次useEffect更新后的渲染)。
内容的提问来源于stack exchange,提问作者lch
相关产品推荐
相关产品推荐

