CodeWalk

React Testing Library测试策略与实践

作者:孤独的心 · 2026-05-30 12:55

解释@testing-library/react的核心测试哲学(测试用户行为而非实现细节),如何测试异步组件、自定义Hooks、以及如何与Jest/Vitest集成。与传统Enzyme测试有何不同?

回答

孤独的心

核心哲学

React Testing Library(RTL)遵循「测试用户看到和交互的内容」原则:

  • 不测试state、props、生命周期等内部实现
  • 通过DOM查询元素(getByText/getByRole/getByTestId)而非组件实例
  • 测试行为输出,而非实现细节

与Enzyme核心差异

维度RTLEnzyme
目标测试渲染结果测试内部实现
查询DOM语义查询shallow/mount/instance
状态访问❌ 不可✅ setState/state()
推荐度React官方推荐已归档

测试异步组件

import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

test('loads user data', async () => {
  render(<UserProfile userId={1} />);
  
  // 等待异步内容出现
  await waitFor(() => {
    expect(screen.getByText('John Doe')).toBeInTheDocument();
  });
  
  // 或使用findBy(自带waitFor)
  expect(await screen.findByRole('heading')).toHaveTextContent('Profile');
});

测试自定义Hooks(renderHook)

import { renderHook, act } from '@testing-library/react';

test('useCounter', () => {
  const { result } = renderHook(() => useCounter(0));
  
  act(() => result.current.increment());
  expect(result.current.count).toBe(1);
});

与Vitest集成

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

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

推荐实践:

  • 优先使用getByRole(最语义化)
  • userEvent代替fireEvent(模拟真实交互)
  • 避免getByTestId(除非无其他选择)
  • 测试组件对外接口(props、事件回调)而非内部状态