关于 vitest
vitest 是使用 Vitest 和 React Testing Library 编写单元测试与集成测试的技能。它规范测试结构、查询方式、交互、异步断言、模拟和文件组织。
主要功能
- 01Given/When/Then 测试结构
- 02按行为组织 describe 块
- 03优先使用无障碍查询
- 04userEvent 交互指南
- 05异步断言与模拟规范
适用场景
- 01测试 React 组件
- 02编写单元和集成测试
- 03组织并审查测试文件
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ prowler-cloud/ prowler --skill vitest安装后在 Claude Code 中输入/vitest确认已加载。
详细介绍
对于端到端测试:使用
prowler-test-ui技能(Playwright)。 此技能涵盖使用 Vitest + React Testing Library 进行的单元/集成测试。
测试结构(必需)
使用带注释的 Given/When/Then(AAA)模式:
it("should update user name when form is submitted", async () => {
// Given - Arrange
const user = userEvent.setup();
const onSubmit = vi.fn();
render(<UserForm onSubmit={onSubmit} />);
// When - Act
await user.type(screen.getByLabelText(/name/i), "John");
await user.click(screen.getByRole("button", { name: /submit/i }));
// Then - Assert
expect(onSubmit).toHaveBeenCalledWith({ name: "John" });
});
Describe 块组织方式
describe("ComponentName", () => {
describe("when [condition]", () => {
it("should [expected behavior]", () => {});
});
});
按行为分组,而不是按方法分组。
查询优先级(必需)
| 优先级 | 查询方式 | 使用场景 |
|---|---|---|
| 1 | getByRole | 按钮、输入框、标题 |
| 2 | getByLabelText | 表单字段 |
| 3 | getByPlaceholderText | 没有标签的输入框 |
| 4 | getByText | 静态文本 |
| 5 | getByTestId | 仅作为最后手段 |
// ✅ GOOD
screen.getByRole("button", { name: /submit/i });
screen.getByLabelText(/email/i);
// ❌ BAD
container.querySelector(".btn-primary");
使用 userEvent 而非 fireEvent(必需)
// ✅ ALWAYS use userEvent
const user = userEvent.setup();
await user.click(button);
await user.type(input, "hello");
// ❌ NEVER use fireEvent for interactions
fireEvent.click(button);
异步测试模式
// ✅ findBy for elements that appear async
const element = await screen.findByText(/loaded/i);
// ✅ waitFor for assertions
await waitFor(() => {
expect(screen.getByText(/success/i)).toBeInTheDocument();
});
// ✅ ONE assertion per waitFor
await waitFor(() => expect(mockFn).toHaveBeenCalled());
await waitFor(() => expect(screen.getByText(/done/i)).toBeVisible());
// ❌ NEVER multiple assertions in waitFor
await waitFor(() => {
expect(mockFn).toHaveBeenCalled();
expect(screen.getByText(/done/i)).toBeVisible(); // Slower failures
});
模拟
// Basic mock
const handleClick = vi.fn();
// Mock with return value
const fetchUser = vi.fn().mockResolvedValue({ name: "John" });
// Always clean up
afterEach(() => {
vi.restoreAllMocks();
});
vi.spyOn 与 vi.mock
| 方法 | 使用时机 |
|---|---|
vi.spyOn | 在不替换的情况下观察(首选) |
vi.mock | 替换整个模块(谨慎使用) |
常用匹配器
// Presence
expect(element).toBeInTheDocument();
expect(element).toBeVisible();
// State
expect(button).toBeDisabled();
expect(input).toHaveValue("text");
expect(checkbox).toBeChecked();
// Content
expect(element).toHaveTextContent(/hello/i);
expect(element).toHaveAttribute("href", "/home");
// Functions
expect(fn).toHaveBeenCalledWith(arg1, arg2);
expect(fn).toHaveBeenCalledTimes(2);
不要测试的内容
// ❌ Internal state
expect(component.state.isLoading).toBe(true);
// ❌ Third-party libraries
expect(axios.get).toHaveBeenCalled();
// ❌ Static content (unless conditional)
expect(screen.getByText("Welcome")).toBeInTheDocument();
// ✅ User-visible behavior
expect(screen.getByRole("button")).toBeDisabled();
文件组织
components/
├── Button/
│ ├── Button.tsx
│ ├── Button.test.tsx # Co-located
│ └── index.ts
命令
pnpm test # Watch mode
pnpm test:run # Single run
pnpm test:coverage # With coverage
pnpm test Button # Filter by name
评论
支持 Markdown