frontend-testing について
Claude が既存のプロジェクト規約に沿ってフロントエンドテストを作成・レビューするためのスキルです。ファイル単位の段階的な進め方、統合テストの指針、Vitest と React Testing Library の利用方法を扱います。
主な機能
- 01フロントエンドテストの作成とレビュー
- 02ファイル単位の段階的な手順
- 03統合テストの指針
- 04モックと非同期テストのパターン
- 05カバレッジとシナリオのチェックリスト
活用シーン
- 01React コンポーネントやフックのテスト作成
- 02フロントエンドテストの網羅性レビュー
- 03フロントエンドディレクトリのカバレッジ改善
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ pageai-pro/ ralph-loop --skill frontend-testingインストール後、Claude Code で/frontend-testingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
assets/component-test.template.tsxhook-test.template.tsutility-test.template.tsreferences/async-testing.mdchecklist.mdcommon-patterns.mdmocking.mdworkflow.md詳細
このスキルを使用すると、確立された規約とベストプラクティスに従って、高品質で包括的なフロントエンドテストを Claude が作成できます。
このスキルを適用するタイミング
ユーザーが次のことを求めている場合に、このスキルを適用します。
- コンポーネント、フック、ユーティリティのテスト作成
- 既存のテストのレビューによる網羅性の確認
- Vitest、React Testing Library、RTL、またはspec ファイルについて言及
- テストカバレッジの改善
- フロントエンドコードのテスト、ユニットテスト、または統合テストについて言及
- フロントエンドのコードベースにおけるテストパターンの理解
次の場合は適用しないでください。
- E2E テスト(Playwright)についての質問
- コードの文脈がなく、概念的な質問のみの場合
クイックリファレンス
技術スタック
| ツール | バージョン | 目的 |
|---|---|---|
| Vitest | 4+ | テストランナー |
| React Testing Library | 16+ | コンポーネントのテスト |
| jsdom | - | テスト環境 |
| TypeScript | 5+ | 型安全性 |
注:この一覧は、プロジェクトの依存関係に合わせて最新の状態に保ってください。
主なコマンド
フィードバックを早く得られるよう、テストスイート全体よりも特定のテストの実行を常に優先してください。
# 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つずつ処理:テストを作成 → テストを実行 → 必要に応じて修正 → 次へ
- 次に進む前に検証:現在のファイルのテストが成功するまで、次のファイルに進まないでください
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 │
└────────────────────────────────────────────────┘
複雑さに基づく順序
複数ファイルをテストする場合は、次の順で処理します。
- 🟢 ユーティリティ関数(最も単純)
- 🟢 カスタムフック
- 🟡 シンプルなコンポーネント(プレゼンテーション用)
- 🟡 中程度のコンポーネント(状態、エフェクト)
- 🔴 複雑なコンポーネント(API、ルーティング)
- 🔴 統合テスト(index ファイルは最後)
先にリファクタリングする場合
- 中程度の複雑さ:テストの前に、より小さな部分に分割します
- 500 行以上:テストの前に分割を検討します
- 依存関係が多い:先にロジックをフックに抽出します
テスト戦略
パス単位のテスト(ディレクトリのテスト)
ディレクトリまたはパスのテストを依頼された場合、そのパス内のすべての内容をテストします。
- ディレクトリ内のすべてのコンポーネント、フック、ユーティリティをテストします(
indexファイルだけではありません) - 段階的なアプローチを使用します。1 ファイルずつ処理し、次に進む前にそれぞれを検証します
- 目標:ディレクトリ内のすべてのファイルで 100% のカバレッジを達成します
統合テストを優先
ディレクトリのテストを作成する場合は、統合テストを優先します。
- ✅ 実際のプロジェクトコンポーネントを直接インポートします(ベースコンポーネントや兄弟コンポーネントを含む)
- ✅ モックする対象は次のものに限定します:API サービス(
@/service/*)、next/navigation、複雑なコンテキストプロバイダー - ❌ ベースコンポーネント(
@/app/components/base/*)をモックしないでください - ❌ 同じディレクトリ内の兄弟コンポーネントや子コンポーネントをモックしないでください
基本原則
1. AAA パターン(Arrange-Act-Assert)
各テストは、次の要素を明確に分けます。
- 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. テストごとに1つの動作
各テストでは、ユーザーから観察できる動作を1つだけ検証します。
// ✅ 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')
必須のテストシナリオ
常に必須(すべてのコンポーネント)
- レンダリング:コンポーネントがクラッシュせずにレンダリングされること
- Props:必須の props、任意の props、デフォルト値
- エッジケース:null、undefined、空の値、境界条件
条件付き(該当する場合)
| 機能 | テストの重点項目 |
|---|---|
useState | 初期状態、遷移、クリーンアップ |
useEffect | 実行、依存関係、クリーンアップ |
| イベントハンドラー | すべての onClick、onChange、onSubmit、キーボード |
| API 呼び出し | 読み込み、成功、エラーの各状態 |
| ルーティング | ナビゲーション、パラメーター、クエリー文字列 |
useCallback/useMemo | 参照の同一性 |
| コンテキスト | プロバイダーの値、コンシューマーの動作 |
| フォーム | バリデーション、送信、エラーの表示 |
カバレッジ目標(ファイルごと)
生成するテストファイルごとに、以下を目指してください。
- ✅ 100% の関数カバレッジ
- ✅ 100% のステートメントカバレッジ
- ✅ >95% のブランチカバレッジ
- ✅ >95% の行カバレッジ
注:複数ファイルからなるディレクトリでは、ファイルを1つずつ処理し、それぞれで完全なカバレッジを達成してください。
references/workflow.mdを参照してください。
詳細ガイド
詳細については、以下を参照してください。
references/workflow.md- 段階的なテストのワークフロー(複数ファイルのテストでは必読)references/mocking.md- モックのパターンとベストプラクティスreferences/async-testing.md- 非同期処理と API 呼び出しreferences/common-patterns.md- よく使われるテストパターンreferences/checklist.md- テスト生成チェックリストと検証手順
プロジェクト設定
vitest.config.ts- Vitest の設定vitest.setup.ts- テスト環境のセットアップ- モジュールは自動的にはモックされません。グローバルモックは
vitest.setup.tsに記述されています(例:react-i18next、next/image)。kyやmimeなどの他のモジュールは、テストファイル内でローカルにモックしてください。