You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何在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. 具体执行时间线

  1. 调用render(<App />),组件挂载触发useEffect
  2. fetchData执行,mock的fetch是微任务,被act等待完成,拿到返回的title
  3. 遇到新增的Promise,把setTimeout的回调推到宏任务队列
  4. act认为所有异步操作已完成,结束等待,进入断言环节
  5. 此时宏任务未执行,setTitle没触发,组件未更新,断言失败
  6. 测试结束后,宏任务才开始执行,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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 18:28:29