用途
本技能面向 React 组件测试,提供基于 Testing Library 与 Vitest 的测试最佳实践与反模式规避指南。适用于以下场景:处理 .test.tsx、.test.ts、.spec.tsx、.spec.ts 文件时;编写或重构组件测试时;以及围绕测试相关问题的工作。
主要能力
核心原则
遵循 Testing Library 的哲学:“像用户使用那样去测试”。具体包括:
- 优先使用基于可访问性的查询——采用真实用户查找元素的方式。
- 不测试实现细节——避免直接访问组件内部状态或方法。
- 模拟真实用户行为——使用 userEvent,避免 fireEvent。
- 显式等待异步处理——使用 waitFor、findBy。
查询优先级
Testing Library 提供多种查询,应按反映可访问性与用户体验的顺序使用。推荐顺序(由高到低):
- getByRole(最高优先)——屏幕阅读器识别元素的方式
- getByLabelText——表单元素(与 label 关联的 input)
- getByPlaceholderText——placeholder 明确时
- getByText——按文本内容查找
- getByDisplayValue——按当前输入值查找(表单元素)
- getByAltText——图片 alt 属性
- getByTitle——title 属性(tooltip 等)
- getByTestId(最后手段)——仅在无法使用其他方式时使用
详细指南见 references/query-priority.md。
用户交互测试
推荐使用 userEvent:
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('사용자가 폼을 제출할 수 있다', async () => {
const user = userEvent.setup();
render(<LoginForm />);
await user.type(screen.getByRole('textbox', { name: /이메일/i }), 'user@example.com');
await user.type(screen.getByLabelText(/비밀번호/i), 'password123');
await user.click(screen.getByRole('button', { name: /로그인/i }));
expect(await screen.findByText(/환영합니다/i)).toBeInTheDocument();
});
要点:调用 userEvent.setup() 后使用;所有 user 方法必须 await;可重现真实浏览器事件顺序(focus、keydown、keyup 等)。
避免使用 fireEvent:
// ❌ 나쁜 예 - fireEvent 사용
fireEvent.click(button);
fireEvent.change(input, { target: { value: "text" } });
// ✅ 좋은 예 - userEvent 사용
await user.click(button);
await user.type(input, "text");
详细指南见 references/user-events.md。
异步处理
推荐使用 findBy 查询:
// ✅ 좋은 예 - findBy 사용
const successMessage = await screen.findByText(/저장되었습니다/i);
expect(successMessage).toBeInTheDocument();
findBy 是 getBy 与 waitFor 的组合(自动等待元素出现)。
使用 waitFor 进行复杂异步验证:
// 복잡한 비동기 검증
await waitFor(() => {
expect(screen.getByRole("alert")).toHaveTextContent("성공");
});
// 여러 조건 검증
await waitFor(() => {
expect(mockFn).toHaveBeenCalledTimes(1);
expect(screen.queryByText(/로딩 중/i)).not.toBeInTheDocument();
});
反模式(避免):
// ❌ 나쁜 예 - 임의의 timeout
await new Promise((resolve) => setTimeout(resolve, 1000));
// ❌ 나쁜 예 - act() 수동 사용 (보통 불필요)
await act(async () => {
// ...
});
// ✅ 좋은 예 - findBy 또는 waitFor
await screen.findByText(/완료/i);
详细指南见 references/async-patterns.md。
常见错误
-
测试实现细节:不要访问内部状态(如 component.state.isOpen)或使用 wrapper.find、simulate 等实现相关方式;应从用户角度验证,如 expect(screen.getByRole("dialog")).toBeVisible()。
-
使用 container 查询:避免 container.querySelector,改用 screen 查询,如 screen.getByRole('button', { name: /제출/i })。
-
不必要的 waitFor:同步元素应直接验证,不需等待。
-
getBy* + toBeInTheDocument():getBy* 找不到元素会抛错,因此 toBeInTheDocument() 技术上是冗余的。但不应移除——它向代码读者传达这是有意的存在性验证,而非重构后残留的查询。
// ❌ 나쁜 예 - assertion 없이 쿼리만 남김
screen.getByRole("button", { name: /제출/i });
// ✅ 좋은 예 - 명시적 assertion으로 의도 전달
expect(screen.getByRole("button", { name: /제출/i })).toBeInTheDocument();
若验证的不是存在性而是其他属性,则无需添加 toBeInTheDocument():
// ✅ 좋은 예 - 다른 matcher로 충분
expect(screen.getByRole("button", { name: /제출/i })).toBeDisabled();
// ❌ 나쁜 예 - 중복 assertion
expect(screen.getByRole("button", { name: /제출/i })).toBeInTheDocument();
expect(screen.getByRole("button", { name: /제출/i })).toBeDisabled();
完整反模式列表见 references/common-mistakes.md。
Vitest + MSW 配置
若需要测试环境配置,参考以下模板:
- assets/vitest.config.ts——Vitest 配置(含 React 插件、覆盖率)
- assets/test-setup.ts——Vitest 全局设置
- assets/msw-setup.ts——MSW handler 及 server 设置
适用场景
本技能适用于 React 项目中编写、重构组件测试,以及处理 .test.tsx、.test.ts、.spec.tsx、.spec.ts 文件的相关工作。
限制
本资料未提供安装命令、集成步骤及收费相关信息,暂未提供。