React 组件测试实战:用 Vitest 与 React Testing Library 把交互固化成用例

test12026-10-151 次阅读

为什么组件要写测试

组件写完能跑起来,不等于改了不会出事。React 组件把状态、事件和 DOM 绑在一起,单元测试能把这些交互固化成可回归的用例。Vitest 启动快、和 Vite 配置复用,配合 React Testing Library(RTL)按"用户视角"查 DOM,比直接断言组件内部 state 更稳。

配置最小环境

// vitest.config.ts
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  test: {
    environment: 'jsdom',
    globals: true,
    setupFiles: './src/test/setup.ts',
  },
})

setup 里引入 @testing-library/jest-dom 的 matcher,让 toBeInTheDocument 这类断言可用:

// src/test/setup.ts
import '@testing-library/jest-dom'

给一个计数器组件写测试

// Counter.tsx
import { useState } from 'react'

export function Counter() {
  const [n, setN] = useState(0)
  return (
    <div>
      <span data-testid="value">{n}</span>
      <button onClick={() => setN(n + 1)}>+1</button>
    </div>
  )
}
// Counter.test.tsx
import { render, screen, fireEvent } from '@testing-library/react'
import { describe, it, expect } from 'vitest'
import { Counter } from './Counter'

describe('Counter', () => {
  it('点击按钮让数值加一', () => {
    render(<Counter />)
    const value = screen.getByTestId('value')
    expect(value).toHaveTextContent('0')
    fireEvent.click(screen.getByText('+1'))
    expect(value).toHaveTextContent('1')
  })
})

异步流程怎么测

组件里发请求再渲染结果,用 findBy 系列等待异步节点,RTL 内部按轮询查询而非 sleep:

it('加载后展示用户名', async () => {
  render(<UserProfile id={1} />)
  expect(await screen.findByText('alice')).toBeInTheDocument()
})

几个容易踩的坑

  • 用 getByText、getByRole 而非查 className,断言才不随样式重构而崩。
  • 每个 it 之间 RTL 自动清理 DOM(globals 开启时),无需手动 unmount。
  • 别断言私有 state,只测用户能观察到的行为和输出。
T

test1

文章作者

为什么组件要写测试 组件写完能跑起来,不等于改了不会出事。React 组件把状态、事件和 DOM 绑在一起,单元测试能...

分类
技术
发布时间
2026-10-15
字数
约 1638 字
阅读
1 次