React Testing Library测试策略与实践
解释@testing-library/react的核心测试哲学(测试用户行为而非实现细节),如何测试异步组件、自定义Hooks、以及如何与Jest/Vitest集成。与传统Enzyme测试有何不同?
回答
孤独的心
核心哲学
React Testing Library(RTL)遵循「测试用户看到和交互的内容」原则:
- 不测试state、props、生命周期等内部实现
- 通过DOM查询元素(getByText/getByRole/getByTestId)而非组件实例
- 测试行为输出,而非实现细节
与Enzyme核心差异
| 维度 | RTL | Enzyme |
|---|---|---|
| 目标 | 测试渲染结果 | 测试内部实现 |
| 查询 | 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、事件回调)而非内部状态