如何消除React测试中'未用act(...)包裹更新'的警告?
问题背景
测试DropDownMultiPick组件时出现如下警告:
An update to DropDownMultiPick inside a test was not wrapped in act(...)
警告指向组件内的这段代码:
10 | const toggleDropdown = () => { >11 | setIsOpen(!isOpen); | ^ 12 | };
组件代码(片段)
const DropDownMultiPick = ({ title, options, selectedOptions, handleOptionsChange }) => { const [isOpen, setIsOpen] = useState(false); const [tempSelectedOptions, setTempSelectedOptions] = useState([...selectedOptions]); const toggleDropdown = () => { setIsOpen(!isOpen); }; ... };
测试代码
const title = "Test Options"; const options = ["Option 1", "Option 2", "Option 3"]; const selectedOptions = ["Option 1"]; const handleOptionsChange = jest.fn(); it("should open and close the dropdown menu on toggle button click", async () => { render( <DropDownMultiPick title={title} options={options} selectedOptions={selectedOptions} handleOptionsChange={handleOptionsChange} /> ); const user = userEvent.setup(); const toggleButton = screen.getByRole("button", { name: "Option 1" }); await user.click(toggleButton); // expect(await screen.findByRole("button", { name: /done/i })).toBeInTheDocument(); await waitFor(() => { expect(screen.getByRole("button", { name: /done/i })).toBeInTheDocument(); }); });
已尝试方案
在组件的toggleDropdown方法中手动用act包裹状态更新,警告消失,但认为这不是最佳方案:
const DropDownMultiPick = ({ title, options, selectedOptions, handleOptionsChange }) => { const [isOpen, setIsOpen] = useState(false); const [tempSelectedOptions, setTempSelectedOptions] = useState([...selectedOptions]); const toggleDropdown = async () => { await act(async () => { setIsOpen(!isOpen); }); }; };
核心问题
消除该警告的最佳解决方案是什么?
完整组件代码
// src/components/batchrec/dropdown-menu-multipick.jsx import React, { useState } from "react"; import "./dropdown-menu-multipick.styles.scss"; const DropDownMultiPick = ({ title, options, selectedOptions, handleOptionsChange }) => { const [isOpen, setIsOpen] = useState(false); const [tempSelectedOptions, setTempSelectedOptions] = useState([...selectedOptions]); const toggleDropdown = () => { setIsOpen(!isOpen); }; const handleItemClick = (option) => { if (tempSelectedOptions.includes(option)) { setTempSelectedOptions(tempSelectedOptions.filter((item) => item !== option)); } else { setTempSelectedOptions([...tempSelectedOptions, option]); } }; const handleClose = () => { setIsOpen(false); }; const handleDone = () => { handleOptionsChange(tempSelectedOptions); handleClose(); }; return ( <div className="dropdown-multipick__container"> <span className="label-title-style-5"> <span className="dropdown-multipick__text label-title-style-1">{title}: </span> <button className="dropdown-multipick__toggle" onClick={toggleDropdown}> {selectedOptions.join(", ") || "Select..."} </button> </span> {isOpen && ( <div className="dropdown-multipick__menu"> {options.map((option, index) => ( <div key={index} className={`dropdown-multipick__item ${ tempSelectedOptions.includes(option) ? "dropdown-multipick__item--selected" : "" }`} onClick={() => handleItemClick(option)} > {option} </div> ))} <button className="dropdown-multipick__done" onClick={handleDone}> Done </button> </div> )} </div> ); }; export default DropDownMultiPick;
最佳解决方案
1. 立即移除组件中的act包裹
act是测试专属API,绝对不能写在业务组件里,这会混淆测试逻辑与业务逻辑,违反React设计规范。
2. 优化测试代码的断言逻辑
你的测试中已经用了await user.click(toggleButton),这本身会自动处理状态更新的act包裹,问题出在后续的断言写法:
推荐方案:用findByRole替代waitFor + getByRole
findByRole是waitFor和getByRole的组合,本身就是异步方法,会自动等待组件完成状态更新和渲染,写法更简洁且符合测试最佳实践:
it("should open and close the dropdown menu on toggle button click", async () => { render( <DropDownMultiPick title={title} options={options} selectedOptions={selectedOptions} handleOptionsChange={handleOptionsChange} /> ); const user = userEvent.setup(); const toggleButton = screen.getByRole("button", { name: "Option 1" }); await user.click(toggleButton); // 直接用findByRole自动等待元素出现 expect(await screen.findByRole("button", { name: /done/i })).toBeInTheDocument(); });
3. 检查并更新测试库版本
确保使用最新版的@testing-library/react和@testing-library/user-event,旧版本可能存在act自动包裹不彻底的问题,执行以下命令更新:
npm install @testing-library/react@latest @testing-library/user-event@latest
为什么手动act能解决问题?
组件内的setIsOpen触发了状态更新,若测试中存在未被测试工具自动包裹的更新操作,就会抛出警告。但手动在组件里加act是错误的——React Testing Library的核心作用就是帮开发者自动处理act的包裹,无需手动在业务代码中添加。
内容的提问来源于stack exchange,提问作者peterphonic
相关产品推荐
相关产品推荐

