mcpField
F
frontend-testing

frontend-testing

github.com/pageai-pro测试与质量33571 次安装18 周前更新
代码仓库

关于 frontend-testing

供 Claude 按既定项目规范编写和审查前端测试的技能,涵盖逐文件迭代测试、偏重集成测试的实践,以及 Vitest 和 React Testing Library 使用指引。

主要功能

  1. 01生成与审查前端测试
  2. 02逐文件增量工作流
  3. 03集成测试指引
  4. 04模拟与异步测试模式
  5. 05覆盖率和场景清单

适用场景

  1. 01为 React 组件或 Hook 编写测试
  2. 02检查前端测试的完整性
  3. 03提升前端目录的测试覆盖率

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/pageai-pro/ralph-loop --skill frontend-testing

安装后在 Claude Code 中输入/frontend-testing确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

assets/
component-test.template.tsx
hook-test.template.ts
utility-test.template.ts
references/
async-testing.md
checklist.md
common-patterns.md
mocking.md
workflow.md

详细介绍

这项技能可帮助 Claude 遵循既定规范和最佳实践,生成高质量、全面的前端测试。

何时应用此技能

在用户出现以下情况时应用此技能:

  • 要求为组件、Hook 或工具函数编写测试
  • 要求检查现有测试是否完整
  • 提到 Vitest、React Testing Library、RTL 或 spec 文件
  • 要求提高测试覆盖率
  • 提到前端代码的测试、单元测试或集成测试
  • 想了解前端代码库中的测试模式

以下情况不要应用:

  • 用户询问的是 E2E 测试(Playwright)
  • 用户只提出概念性问题,没有代码上下文

快速参考

技术栈
工具版本用途
Vitest4+测试运行器
React Testing Library16+组件测试
jsdom-测试环境
TypeScript5+类型安全

注意:请根据项目的依赖项及时更新此列表。

关键命令

为了更快获得反馈,始终优先运行指定测试,而不是整个测试套件:

# Run ALL tests (avoid during development)
npx vitest run

# ✅ PREFERRED: Run specific file
npx vitest run src/components/Button.spec.tsx

# ✅ PREFERRED: Run tests matching a pattern
npx vitest run --grep "Button"
npx vitest run -t "should render"

# ✅ PREFERRED: Run specific describe block
npx vitest run --grep "Button > Rendering"

# ✅ Run tests in a directory
npx vitest run src/components/

# ✅ Run single test by name
npx vitest run -t "should disable button when loading"
监视模式(后台测试)

使用监视模式,高效地迭代开发:

# ✅ Watch mode - reruns on file changes
npx vitest

# ✅ Watch specific file
npx vitest src/components/Button.spec.tsx

# ✅ Watch tests matching pattern
npx vitest --grep "Button"
文件命名
  • 测试文件:ComponentName.spec.tsx(与组件放在同一目录)

测试结构模板

import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import Component from './index'

// ✅ Import real project components (DO NOT mock these)
// import Loading from '@/app/components/base/loading'
// import { ChildComponent } from './child-component'

// ✅ Mock external dependencies only
vi.mock('@/service/api')
vi.mock('next/navigation', () => ({
  useRouter: () => ({ push: vi.fn() }),
  usePathname: () => '/test',
}))

// Shared state for mocks (if needed)
let mockSharedState = false

describe('ComponentName', () => {
  beforeEach(() => {
    vi.clearAllMocks()  // ✅ Reset mocks BEFORE each test
    mockSharedState = false  // ✅ Reset shared state
  })

  // Rendering tests (REQUIRED)
  describe('Rendering', () => {
    it('should render without crashing', () => {
      // Arrange
      const props = { title: 'Test' }

      // Act
      render(<Component {...props} />)

      // Assert
      expect(screen.getByText('Test')).toBeInTheDocument()
    })
  })

  // Props tests (REQUIRED)
  describe('Props', () => {
    it('should apply custom className', () => {
      render(<Component className="custom" />)
      expect(screen.getByRole('button')).toHaveClass('custom')
    })
  })

  // User Interactions
  describe('User Interactions', () => {
    it('should handle click events', () => {
      const handleClick = vi.fn()
      render(<Component onClick={handleClick} />)

      fireEvent.click(screen.getByRole('button'))

      expect(handleClick).toHaveBeenCalledTimes(1)
    })
  })

  // Edge Cases (REQUIRED)
  describe('Edge Cases', () => {
    it('should handle null data', () => {
      render(<Component data={null} />)
      expect(screen.getByText(/no data/i)).toBeInTheDocument()
    })

    it('should handle empty array', () => {
      render(<Component items={[]} />)
      expect(screen.getByText(/empty/i)).toBeInTheDocument()
    })
  })
})

测试工作流(重要)

⚠️ 必须采用增量方式

绝不要一次性生成所有测试文件。 对于复杂组件或包含多个文件的目录:

  1. 分析并规划:列出所有文件,按复杂度从低到高排序
  2. 逐个处理:编写测试 → 运行测试 → 必要时修复 → 继续下一个
  3. 继续前先验证:当前文件的测试通过之前,不要处理下一个文件
For each file:
  ┌────────────────────────────────────────────────┐
  │ 1. Write test                                  │
  │ 2. Run: npm run test-unit <file>.spec.tsx      │
  │ 3. PASS? → Mark complete, next file            │
  │    FAIL? → Fix first, then continue            │
  └────────────────────────────────────────────────┘
按复杂度排序

对多个文件进行测试时,按以下顺序处理:

  1. 🟢 工具函数(最简单)
  2. 🟢 自定义 Hook
  3. 🟡 简单组件(展示型)
  4. 🟡 中等复杂度组件(包含状态、效果)
  5. 🔴 复杂组件(涉及 API、路由)
  6. 🔴 集成测试(index 文件,最后处理)
何时先重构
  • 中等复杂度:测试前拆分为更小的部分
  • 500+ 行:考虑在测试前拆分
  • 依赖项较多:先将逻辑提取到 Hook 中

测试策略

按路径测试(目录测试)

分配到某个目录/路径的测试任务时,测试该路径下的所有内容:

  • 测试目录中的所有组件、Hook 和工具函数(不只是 index 文件)
  • 采用增量方式:一次处理一个文件,每个文件都要先验证再继续
  • 目标:覆盖目录中的所有文件,覆盖率达到 100%
优先进行集成测试

为目录编写测试时,优先采用集成测试:

  • ✅ 直接导入项目中的真实组件(包括基础组件和同级组件)
  • ✅ 仅模拟:API 服务(@/service/*)、next/navigation、复杂的上下文提供器
  • ❌ 不要模拟基础组件(@/app/components/base/*)
  • ❌ 不要模拟同一目录中的同级/子组件

核心原则

1. AAA 模式(准备-执行-断言)

每项测试都应清晰地区分:

  • 准备(Arrange):设置测试数据并渲染组件
  • 执行(Act):执行用户操作
  • 断言(Assert):验证预期结果
2. 黑盒测试
  • 测试可观察到的行为,而非实现细节
  • 使用语义化查询(getByRole、getByLabelText)
  • 避免直接测试内部状态
  • 断言中优先使用模式匹配,而非硬编码字符串:
// ❌ Avoid: hardcoded text assertions
expect(screen.getByText('Loading...')).toBeInTheDocument()

// ✅ Better: role-based queries
expect(screen.getByRole('status')).toBeInTheDocument()

// ✅ Better: pattern matching
expect(screen.getByText(/loading/i)).toBeInTheDocument()
3. 每项测试只验证一种行为

每项测试只验证一种用户可观察到的行为:

// ✅ Good: One behavior
it('should disable button when loading', () => {
  render(<Button loading />)
  expect(screen.getByRole('button')).toBeDisabled()
})

// ❌ Bad: Multiple behaviors
it('should handle loading state', () => {
  render(<Button loading />)
  expect(screen.getByRole('button')).toBeDisabled()
  expect(screen.getByText('Loading...')).toBeInTheDocument()
  expect(screen.getByRole('button')).toHaveClass('loading')
})
4. 语义化命名

使用 should <behavior> when <condition>:

it('should show error message when validation fails')
it('should call onSubmit when form is valid')
it('should disable input when isReadOnly is true')

必需的测试场景

始终必需(所有组件)
  1. 渲染:组件渲染时不会崩溃
  2. Props:必需的 props、可选的 props、默认值
  3. 边界情况:null、undefined、空值、边界条件
条件性要求(存在时)
功能测试重点
useState初始状态、状态转换、清理
useEffect执行、依赖项、清理
事件处理程序所有 onClick、onChange、onSubmit、键盘操作
API 调用加载、成功、错误状态
路由导航、参数、查询字符串
useCallback/useMemo引用相等性
ContextProvider 值、消费者行为
表单验证、提交、错误显示

覆盖率目标(每个文件)

对于生成的每个测试文件,目标是:

  • ✅ 100% 函数覆盖率
  • ✅ 100% 语句覆盖率
  • ✅ >95% 分支覆盖率
  • ✅ >95% 行覆盖率

注意:对于包含多个文件的目录,每次处理一个文件,并确保每个文件都达到完整覆盖率。请参阅 references/workflow.md。

详细指南

如需了解更多详细信息,请参阅:

  • references/workflow.md - 增量测试工作流(测试多个文件时必须阅读)
  • references/mocking.md - Mock 模式和最佳实践
  • references/async-testing.md - 异步操作和 API 调用
  • references/common-patterns.md - 常用测试模式
  • references/checklist.md - 测试生成检查清单和验证步骤
项目配置
  • vitest.config.ts - Vitest 配置
  • vitest.setup.ts - 测试环境设置
  • 不会自动 Mock 模块。全局 Mock 位于 vitest.setup.ts 中(例如 react-i18next、next/image);请在测试文件中局部 Mock 其他模块,例如 ky 或 mime。

评论

支持 Markdown