测试自定义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
相关产品推荐
相关产品推荐

