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

React测试触发状态更新时,何时需要使用act()?

为什么在假定时器触发状态更新时需要手动使用act()?

我用Vitest搭配@testing-library/react做组件测试,两个测试都能通过,但第二个测试里,当触发超时函数内的setBaz时,会弹出“需使用act()”的警告。点击Foo、Bar按钮触发的状态更新不会有这个问题,只有定时器里的状态更新会触发警告。

组件代码

export function Foo() {
  const [foo, setFoo] = useState('');
  const [bar, setBar] = useState('');
  const [baz, setBaz] = useState('');

  useEffect(() => {
    if (bar) setTimeout(() => setBaz('baz'), 5000);
  }, [bar]);

  return (
    <>
      <button onClick={() => setFoo('foo')}>Foo: {foo}</button>
      <button onClick={() => setBar('bar')}>Bar: {bar}</button>
      <div>Baz: {baz}</div>
    </>
  );
}

测试代码

test('should update foo', async () => {
  const user = userEvent.setup();

  render(<Foo />);

  const fooBtn = screen.getByText(/Foo:/);
  await user.click(fooBtn);

  expect(fooBtn.innerHTML).toEqual('Foo: foo');
});

/*
  警告:An update to Foo inside a test was not wrapped in act(...).
  When testing, code that causes React state updates should be wrapped into act(...):
*/
test('should update baz after timer', async () => {
  vi.useFakeTimers({ shouldAdvanceTime: true });
  const user = userEvent.setup();

  render(<Foo />);

  const barBtn = screen.getByText(/Bar:/);
  await user.click(barBtn);

  vi.advanceTimersByTime(5000); // 这里会触发警告

  await waitFor(() => {
    const bazEl = screen.getByText(/Baz:/);
    expect(bazEl.innerHTML).toEqual('Baz: baz');
  });
});

把vi.advanceTimersByTime(5000)包裹在act(() => vi.advanceTimersByTime(5000))里就能消除警告,下面说清这类场景下什么时候需要用act():


先搞懂act()的作用

React的act()本质是用来同步组件的状态更新和渲染——它会等待所有包裹在内的代码引发的组件渲染、状态更新完成后,再执行后续逻辑,让测试的行为和真实浏览器里的用户操作保持一致。

为什么有的场景不用手动写act()?

@testing-library/react提供的大部分工具(比如render、userEvent.click、waitFor)已经内部自动封装了act()。比如第一个测试里的await user.click(fooBtn),点击触发的setFoo状态更新,已经被userEvent的click方法自动包裹在act里了,所以不会出警告。

什么时候必须手动用act()?

当你执行的操作不在Testing Library工具的覆盖范围内,但会直接触发组件的状态更新时,就需要手动用act()包裹:

  • 直接操作假定时器(比如vi.advanceTimersByTime、jest.runAllTimers),因为推进定时器会触发内部的回调(比如这里的setBaz),进而引发状态更新
  • 手动调用组件内部的状态更新函数(比如直接调用setFoo)
  • 手动触发异步回调(比如直接调用setTimeout里的回调函数)

简单说:只要你的测试代码里有自己写的、会触发组件状态更新,但没被Testing Library工具自动处理的逻辑,就需要用act()把它包起来。

修正后的测试代码

test('should update baz after timer', async () => {
  vi.useFakeTimers({ shouldAdvanceTime: true });
  const user = userEvent.setup();

  render(<Foo />);

  const barBtn = screen.getByText(/Bar:/);
  await user.click(barBtn);

  // 用act包裹推进定时器的操作
  act(() => {
    vi.advanceTimersByTime(5000);
  });

  await waitFor(() => {
    const bazEl = screen.getByText(/Baz:/);
    expect(bazEl.innerHTML).toEqual('Baz: baz');
  });
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 08:05:21