解决React Testing Library中的act()警告问题
问题
测试React组件时,用例能正常通过,但终端持续收到Bird组件的act警告:
Warning: An update to Bird inside a test was not wrapped in act(...).
警告指向handleRemoveBird函数里的setShowModal(true)状态更新操作。想明确两个问题:
- 这是否和关联Modal组件的状态在测试外部更新有关?
- 不管原因是什么,需要用
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警告。
具体修复操作
你需要做两处关键调整:
- 等待用户点击操作完成:
user.click是异步函数,必须用await包裹,确保状态更新和渲染都在act上下文内执行。 - 简化断言逻辑:用
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
相关产品推荐
相关产品推荐

