SuperAPI AI 工具集

testing

测试与质量

查看项目

更新于 10-15 15:10

技能介绍

用途

本技能面向 React 组件测试,提供基于 Testing Library 与 Vitest 的测试最佳实践与反模式规避指南。适用于以下场景:处理 .test.tsx、.test.ts、.spec.tsx、.spec.ts 文件时;编写或重构组件测试时;以及围绕测试相关问题的工作。

主要能力

核心原则

遵循 Testing Library 的哲学:“像用户使用那样去测试”。具体包括:

  • 优先使用基于可访问性的查询——采用真实用户查找元素的方式。
  • 不测试实现细节——避免直接访问组件内部状态或方法。
  • 模拟真实用户行为——使用 userEvent,避免 fireEvent。
  • 显式等待异步处理——使用 waitFor、findBy。

查询优先级

Testing Library 提供多种查询,应按反映可访问性与用户体验的顺序使用。推荐顺序(由高到低):

  1. getByRole(最高优先)——屏幕阅读器识别元素的方式
  2. getByLabelText——表单元素(与 label 关联的 input)
  3. getByPlaceholderText——placeholder 明确时
  4. getByText——按文本内容查找
  5. getByDisplayValue——按当前输入值查找(表单元素)
  6. getByAltText——图片 alt 属性
  7. getByTitle——title 属性(tooltip 等)
  8. 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。

常见错误

  1. 测试实现细节:不要访问内部状态(如 component.state.isOpen)或使用 wrapper.find、simulate 等实现相关方式;应从用户角度验证,如 expect(screen.getByRole("dialog")).toBeVisible()。

  2. 使用 container 查询:避免 container.querySelector,改用 screen 查询,如 screen.getByRole('button', { name: /제출/i })。

  3. 不必要的 waitFor:同步元素应直接验证,不需等待。

  4. 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 文件的相关工作。

限制

本资料未提供安装命令、集成步骤及收费相关信息,暂未提供。