mcpField
V
vitest

vitest

github.com/prowler-cloud测试与质量15.1k67 次安装1 天前更新
代码仓库

关于 vitest

vitest 是使用 Vitest 和 React Testing Library 编写单元测试与集成测试的技能。它规范测试结构、查询方式、交互、异步断言、模拟和文件组织。

主要功能

  1. 01Given/When/Then 测试结构
  2. 02按行为组织 describe 块
  3. 03优先使用无障碍查询
  4. 04userEvent 交互指南
  5. 05异步断言与模拟规范

适用场景

  1. 01测试 React 组件
  2. 02编写单元和集成测试
  3. 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]", () => {});
  });
});

按行为分组,而不是按方法分组。


查询优先级(必需)

优先级查询方式使用场景
1getByRole按钮、输入框、标题
2getByLabelText表单字段
3getByPlaceholderText没有标签的输入框
4getByText静态文本
5getByTestId仅作为最后手段
// ✅ 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