为何在React组件中新增Promise会导致await act()测试失败?
为什么新增
setTimeout的Promise会导致React测试失败? 初始可运行代码(组件+测试)
function App() { const [title, setTitle] = useState(""); useEffect(() => { const fetchData = async () => { const response = await fetch( "https://jsonplaceholder.typicode.com/posts/1" ); const { title } = await response.json(); setTitle(title); }; fetchData(); }, []); return <> {title} </>; }; it("renders title", async () => { jest.spyOn(window, "fetch").mockResolvedValue({ json: async () => ({ title: "Fetched" }), }); await act(async () => { render(<App />); }); expect(screen.getByText("Fetched")).toBeInTheDocument(); });
修改后导致测试失败的组件代码
function App() { const [title, setTitle] = useState(""); useEffect(() => { const fetchData = async () => { const response = await fetch( "https://jsonplaceholder.typicode.com/posts/1" ); const { title } = await response.json(); await new Promise((resolve) => setTimeout(resolve, 0)); // 新增此行导致测试失败 setTitle(title); // 此操作在测试结束后才执行 }; fetchData(); }, []); return <> {title} </>; };
核心原因解析
1. JavaScript事件循环的任务优先级
await new Promise(resolve => setTimeout(resolve, 0))本质是把resolve回调扔进宏任务队列。而fetch、response.json()这类Promise属于微任务,微任务会在当前同步代码执行完毕后立刻处理,宏任务则要等所有微任务处理完、进入下一轮事件循环才会执行。
2. act的处理边界
测试里的await act(...)只会等待React的同步渲染流程,以及组件内部的微任务完成,不会等待宏任务队列里的内容。当act执行结束进入expect断言时,setTitle(title)还躺在宏任务队列里没执行,组件的title还是初始空字符串,自然找不到"Fetched"文本,测试直接失败。
3. 具体执行时间线
- 调用
render(<App />),组件挂载触发useEffect fetchData执行,mock的fetch是微任务,被act等待完成,拿到返回的title- 遇到新增的Promise,把
setTimeout的回调推到宏任务队列 act认为所有异步操作已完成,结束等待,进入断言环节- 此时宏任务未执行,
setTitle没触发,组件未更新,断言失败 - 测试结束后,宏任务才开始执行,
setTitle被调用,但测试已经跑完了
修复方案
要让测试通过,需要等待宏任务执行完成,推荐用React Testing Library的waitFor工具,它会自动轮询直到目标元素出现或超时:
it("renders title", async () => { jest.spyOn(window, "fetch").mockResolvedValue({ json: async () => ({ title: "Fetched" }), }); render(<App />); await waitFor(() => { expect(screen.getByText("Fetched")).toBeInTheDocument(); }); });
或者也可以手动等待宏任务执行:
it("renders title", async () => { jest.spyOn(window, "fetch").mockResolvedValue({ json: async () => ({ title: "Fetched" }), }); render(<App />); // 等待宏任务队列执行 await new Promise(resolve => setTimeout(resolve, 0)); expect(screen.getByText("Fetched")).toBeInTheDocument(); });
内容的提问来源于stack exchange,提问作者lch
相关产品推荐
相关产品推荐

