测试金字塔
大量单元测试(快、隔离)、适量组件测试、少量 E2E(覆盖关键路径)。过多 E2E 会慢且脆弱。
断言"用户能看到什么、点了发生什么",而非内部 state 值。实现改了测试也不该崩。
Vitest 单测
Vitest 与 Vite 同源、API 兼容 Jest,启动快。用 describe/it/expect 写纯函数测试。
import { describe, it, expect } from 'vitest';
import { sum } from './math';
describe('sum', () => {
it('加两个正数', () => expect(sum(1, 2)).toBe(3));
});Testing Library
Testing Library 鼓励"像用户一样查询 DOM":按角色、文本、标签查找元素,而非依赖测试 id 或内部类名。
import { render, screen } from '@testing-library/react';
render(<Greeting name="A" />);
expect(screen.getByText('Hello A')).toBeInTheDocument();优先 getByRole(无障碍友好)> getByLabelText > getByText > getByTestId(最后手段)。
组件交互
用 fireEvent 或 userEvent 模拟点击、输入,再断言 UI 变化,覆盖真实交互路径。
const btn = screen.getByRole('button', { name: '加一' });
await userEvent.click(btn);
expect(screen.getByText('1')).toBeInTheDocument();Mock 网络
组件测试不应真发请求。用 MSW(Mock Service Worker)或 vi.mock 拦截 fetch,返回固定数据,保证确定与速度。
import { http, HttpResponse } from 'msw';
const handlers = [
http.get('/api/user', () => HttpResponse.json({ name: 'A' }))
];Playwright E2E
Playwright 驱动真实浏览器跑完整用户流程(登录→下单),最接近线上行为,用于守护关键链路。
import { test, expect } from '@playwright/test';
test('登录后可看到主页', async ({ page }) => {
await page.goto('/login');
await page.fill('#email', 'a@b.com');
await page.click('button[type=submit]');
await expect(page).toHaveURL('/home');
});E2E 慢且易碎,只覆盖核心业务流(登录、下单、支付)。逻辑细节交给单测与组件测试。
覆盖率
Vitest 内置覆盖率(基于 V8)。关注关键路径分支,而非盲目追求 100%。
vitest run --coverage常见陷阱与 FAQ
改用 Testing Library 按用户视角查询,断言可见结果。组件内部状态变化不该是断言对象。
用 MSW 在测试环境拦截网络,返回 mock 数据,既快又确定,还能模拟错误场景。
避免依赖具体等待时间,用 waitFor/断言可见性做显式等待;用测试专用账号与数据,隔离环境。