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

解决React Testing Library中的act()警告问题

问题

测试React组件时,用例能正常通过,但终端持续收到Bird组件的act警告:

Warning: An update to Bird inside a test was not wrapped in act(...).

警告指向handleRemoveBird函数里的setShowModal(true)状态更新操作。想明确两个问题:

  1. 这是否和关联Modal组件的状态在测试外部更新有关?
  2. 不管原因是什么,需要用await/waitFor哪些操作来修复这个警告?

测试代码

import { screen, waitFor } from "@testing-library/react";
import user from "@testing-library/user-event";
import { renderWithProviders } from "../utils/utils-for-tests";
import Bird from "../components/Bird";

const modalContainerMock = document.createElement("div");
modalContainerMock.setAttribute("class", "modal-container");
document.body.appendChild(modalContainerMock);

const birdImgMock = jest.mock("../../components/BirdImg", () => {
  return () => {
    return "Bird Img Component";
  };
});

const renderComponent = (bird) => {
  renderWithProviders(<Bird bird={bird} />);
};

const bird = {
  id: "1",
  name: "blue tit",
  number: "5",
};

test("shows modal before removal", async () => {
  renderComponent(bird);
  const removeButton = await screen.findByTestId(`removeButton-${bird.id}`);
  expect(removeButton).toBeInTheDocument();

  user.click(removeButton);

  await waitFor(() => {
    expect(screen.getByTestId("modal")).toBeInTheDocument();
  });
});
...

组件代码

import { useState } from "react";
import { RxCross2 } from "react-icons/rx";
import { useRemoveBirdMutation } from "../store";
import Panel from "./Panel";
import Button from "./Button";
import Modal from "./Modal";
import Sightings from "./Sightings";
import BirdImg from "./BirdImg";

function Bird({ bird }) {
  const [removeBird, removeBirdResults] = useRemoveBirdMutation();
  const [showModal, setShowModal] = useState(false);

  const handleRemoveBird = () => {
    setShowModal(true);
  };
  const handleClose = () => {
    setShowModal(false);
  };
  const handleDelete = () => {
    removeBird(bird);
    setShowModal(false);
  };

  const actionBar = (
    <>
      <Button primary onClick={handleClose}>
        Keep
      </Button>

      <Button secondary onClick={handleDelete}>
        Delete
      </Button>
    </>
  );

  const modal = (
    <Modal actionBar={actionBar} onClose={handleClose}>
      <p>Are you sure you want to delete this bird?</p>
    </Modal>
  );
  return (
    <>
      <Panel
        primary
        data-testid="bird"
        key={bird.id}
        className="grid grid-cols-5 md:gap-5"
      >
        <div className="col-span-2">
          <BirdImg name={bird.name} />
        </div>

        <div className="col-span-2 grid content-around -ml-4 md:-ml-8">
          <h4 className="capitalize text-lg font-bold md:text-3xl ">
            {bird.name}
          </h4>
          <div>
            <Sightings bird={bird} />
          </div>
        </div>
        <div className="col-span-1 justify-self-end">
          <Button
            onClick={handleRemoveBird}
            remove
            loading={removeBirdResults.isLoading}
            data-testid={`removeButton-${bird.id}`}
          >
            <RxCross2 />
          </Button>
        </div>
      </Panel>
      {showModal && modal}
    </>
  );
}
export default Bird;
解决方法

警告原因说明

这个警告和Modal状态在测试外部更新无关,核心问题是测试中触发状态更新后,没有等待React完成所有渲染流程就结束测试。点击删除按钮后,setShowModal(true)会触发Bird组件重渲染并挂载Modal,但你没有用await等待user.click这个异步操作完成,导致React在测试结束后仍在处理状态更新,从而抛出act警告。

具体修复操作

你需要做两处关键调整:

  1. 等待用户点击操作完成:user.click是异步函数,必须用await包裹,确保状态更新和渲染都在act上下文内执行。
  2. 简化断言逻辑:用await screen.findByTestId("modal")替代waitFor,因为findBy*系列方法本身内置了等待机制,无需额外嵌套waitFor。

修改后的测试代码:

test("shows modal before removal", async () => {
  renderComponent(bird);
  const removeButton = await screen.findByTestId(`removeButton-${bird.id}`);
  expect(removeButton).toBeInTheDocument();

  // 等待点击操作完成,确保状态更新触发的渲染都完成
  await user.click(removeButton);

  // 直接用findByTestId等待Modal出现,自动处理渲染异步
  const modal = await screen.findByTestId("modal");
  expect(modal).toBeInTheDocument();
});

额外注意事项

  • 如果你的Modal组件内部有额外异步逻辑(比如入场动画、防抖操作),可以保留waitFor来等待这些副作用完成,但当前场景下findBy*足够覆盖需求。
  • 确认renderWithProviders没有遗漏同步初始化逻辑,比如Redux Provider的状态初始化是否同步完成,避免因初始化异步导致的act警告。

内容的提问来源于stack exchange,提问作者user20257043

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 18:24:57