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

如何消除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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 16:44:53