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

React渲染时状态立即变更,测试需用act包裹的问题

解决iframe组件测试中的act警告问题

我们有一个被“overlay”加载指示器组件包裹的iframe组件,通过原生onload事件清除加载状态:

export const Frame = ({ className, src }: FrameProps): JSX.Element => {
  const [status, setStatus] = useState<OverlayStatuses>('loading')

  const onLoad = useCallback(() => {
    setStatus('success')
  }, [])

  return (
    <Overlay status={status}>
      <iframe className={clsx(styles.frame, className)} onLoad={onLoad} src={src.toString()} />
    </Overlay>
  )
}

在使用testing-library/react和vitest做单元测试时,用about:blank作为iframe的src会立即触发onload事件,导致状态更新未被act包裹,出现警告:

测试代码:

describe('frame', () => {
  it('mounts and matches snapshot', () => {
    const { container } = render(<Frame src={new URL('about:blank')} />)

    expect(container).toBeInTheDocument()
    expect(container).toMatchSnapshot()
  })
})

警告信息:

stderr | app/components/frame/tests/frame.spec.tsx > frame > mounts and matches snapshot
Warning: An update to Frame inside a test was not wrapped in act(...).

When testing, code that causes React state updates should be wrapped into act(...):

act(() => {
  /* fire events that update state */
});
/* assert on the output */

This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
    at Frame (C:\...\app\components\frame\frame.tsx:13:18)

使用真实外部URL(如http://example.com)时,onload不会立即触发,警告会消失,但自动化测试中不适合依赖真实外部网站。

解决方案

1. 用waitFor等待状态更新完成

waitFor会自动将状态更新逻辑包裹到act中,确保测试捕获到组件的最终状态:

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

describe('frame', () => {
  it('mounts and matches snapshot', async () => {
    const { container } = render(<Frame src={new URL('about:blank')} />)
    
    await waitFor(() => {
      expect(container).toMatchSnapshot()
    })
    
    expect(container).toBeInTheDocument()
  })
})

2. 手动触发onload事件

通过fireEvent手动控制onload的触发时机,testing-library会自动用act包裹事件触发流程:

import { fireEvent } from '@testing-library/react'

describe('frame', () => {
  it('mounts and matches snapshot', () => {
    const { container, getByRole } = render(<Frame src={new URL('about:blank')} />)
    const iframe = getByRole('iframe') as HTMLIFrameElement
    
    fireEvent.load(iframe)
    
    expect(container).toBeInTheDocument()
    expect(container).toMatchSnapshot()
  })
})

3. Mock状态初始值(快速测试方案)

如果不需要测试加载状态切换逻辑,可以直接mockuseState让初始状态为success,跳过状态更新流程:

import { vi } from 'vitest'

vi.mock('react', () => ({
  ...vi.importActual('react'),
  useState: (initial: any) => [initial === 'loading' ? 'success' : initial, vi.fn()]
}))

describe('frame', () => {
  it('mounts and matches snapshot', () => {
    const { container } = render(<Frame src={new URL('about:blank')} />)
    
    expect(container).toBeInTheDocument()
    expect(container).toMatchSnapshot()
  })
})

方案选择

  • 优先选waitFor:最贴近真实浏览器行为,能完整测试加载状态切换流程
  • 选手动触发事件:适合需要精准控制状态更新时机的场景
  • 选Mock状态:适合只需要验证组件渲染结构的快速测试

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 00:22:40