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

测试自定义Hook状态更新时为何需要act()?

关于act()的自动包裹与手动调用场景

首先明确:Testing Library的自动act包裹仅覆盖它自身提供的渲染和交互API,直接调用自定义Hook返回的状态更新函数(比如你的increment/decrement)时,需要手动用act()包裹。

自动act的适用场景

  • 使用render、renderHook这类渲染API时,内部已经自动用act包裹渲染流程,确保组件/Hook的初始渲染和状态同步完成。
  • 使用fireEvent、userEvent这类官方交互工具模拟用户操作时,这些API也会自动用act包裹,保证触发的状态更新完成后再执行后续代码。

需要手动调用act的场景

当你直接调用Hook返回的状态更新方法(比如result.current.increment())时,属于手动触发React状态更新,并没有经过Testing Library的交互API,因此不会被自动包裹。React的状态更新是异步的,若不用act包裹,断言会在状态更新完成前执行,导致拿到旧的状态值(比如你测试中预期8但实际得到5)。

正确的测试写法

你可以用act包裹状态更新调用,确保状态完全更新后再断言:

test('state update in isolation', () => {
  const { result } = renderHook((props) => useCounter(props), {
    initialProps: { init: 5, by: 3 },
  });
  expect(result.current.count).toBe(5);
  
  act(() => {
    result.current.increment();
  });
  expect(result.current.count).toBe(8);
  
  act(() => {
    result.current.decrement();
  });
  expect(result.current.count).toBe(5);
});

另外,也可以使用waitFor来等待状态更新(适用于更复杂的异步场景):

import { waitFor } from '@testing-library/react';

test('state update in isolation', async () => {
  const { result } = renderHook((props) => useCounter(props), {
    initialProps: { init: 5, by: 3 },
  });
  expect(result.current.count).toBe(5);
  
  result.current.increment();
  await waitFor(() => {
    expect(result.current.count).toBe(8);
  });
});

补充说明

你的测试中使用的renderHook和act来自@testing-library/react是正确的,这和库本身无关,完全是act的适用规则导致的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 19:06:24