フロントエンドテスティング完全ガイド – Jest・Vitest・Playwright
テスティングの戦略
フロントエンドテスティングは品質保証の基盤です。ユニットテスト、統合テスト、E2E テストの 3 層構造により、本番環境でのバグを最小限に抑えられます。Jest、Vitest、Playwright という 3 つの主要なテストフレームワークの特徴と使い分けをまとめました。
対象となる方
- フロントエンドエンジニア、テスト品質を重視する開発チーム
- テスト導入を検討している方
- 既存テストの改善を目指す方
※ このドキュメントは React 環境を想定しています。
テストピラミッドの理解
テストの 3 階層
E2E テスト(少数)
/ \
/ \
/統合テスト\
/ \
/ユニットテスト\
/ \
ユニットテスト:関数やコンポーネント単位の最小単位のテスト。最も数が多く、実行も高速。
統合テスト:複数のコンポーネントやモジュール組み合わせた機能テスト。
E2E テスト:エンドユーザーの操作をシミュレートしたテスト。最も重要だが、実行時間が長い。
Jest – フロントエンド標準テストフレームワーク
Jest の特徴
Jest は Facebook(Meta)が開発したテストフレームワークで、Create React App にデフォルトで含まれています。ゼロコンフィグで動作し、スナップショットテストなど独特の機能があります。
// Jest セットアップ
npm install --save-dev jest @testing-library/react @testing-library/jest-dom
// jest.config.js
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['/src/setupTests.js'],
};
Jest でのユニットテスト
// sum.js - テスト対象
export function sum(a, b) {
return a + b;
}
// sum.test.js - テストコード
import { sum } from './sum';
describe('sum関数', () => {
test('正の数の合計を計算', () => {
expect(sum(1, 2)).toBe(3);
});
test('負の数に対応', () => {
expect(sum(-1, -2)).toBe(-3);
});
test('0を返す', () => {
expect(sum(0, 0)).toBe(0);
});
});
Jest でのコンポーネントテスト
// Button.jsx
export function Button({ label, onClick, disabled }) {
return (
<button onClick={onClick} disabled={disabled}>
{label}
</button>
);
}
// Button.test.jsx
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';
describe('Button コンポーネント', () => {
test('ボタンがレンダリングされる', () => {
render(<Button label="Click me" onClick={() => {}} />);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
test('クリックで onClick が呼ばれる', () => {
const handleClick = jest.fn();
render(<Button label="Click" onClick={handleClick} />);
fireEvent.click(screen.getByText('Click'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
test('disabled 属性が適用される', () => {
render(<Button label="Disabled" onClick={() => {}} disabled />);
expect(screen.getByText('Disabled')).toBeDisabled();
});
});
Jest のスナップショットテスト
// Card.test.jsx
import { render } from '@testing-library/react';
import { Card } from './Card';
describe('Card コンポーネント', () => {
test('スナップショットテスト', () => {
const { container } = render(
<Card title="Test" description="Description" />
);
expect(container).toMatchSnapshot();
});
});
注意:スナップショットテストは UI の変更を検出しますが、意図しない変更をキャッチするためのツールです。頻繁に更新される必要があります。
Vitest – 高速なテストランナー
Vitest の特徴
Vitest は Jest 互換で、Vite による高速テスト実行が特徴です。Jest より 2~3 倍高速で実行でき、HMR(ホットモジュールリプレースメント)でテストを自動実行できます。
// vitest.config.js
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: './src/setup.ts',
},
})
Vitest でのテスト実行
# Vitest セットアップ
npm install --save-dev vitest jsdom @testing-library/react
# ウォッチモードで実行(テスト自動再実行)
npm run test
# テストカバレッジを表示
npm run test -- --coverage
# UI でテスト結果を表示
npm run test -- --ui
Vitest のメリット
- 高速実行:Jest より 2~3 倍高速
- Jest 互換:既存 Jest テストを流用可能
- HMR 対応:ファイル保存で自動実行
- UI 表示:ブラウザでテスト結果を可視化
- Vite との統合:設定が共有できる
Playwright – E2E テストの最強ツール
Playwright の特徴
Playwright は Chromium、Firefox、WebKit という 3 つのブラウザエンジンをコントロールし、クロスブラウザ E2E テストを実現します。実際のブラウザでエンドユーザーの操作をシミュレートできます。
# Playwright セットアップ
npm install -D @playwright/test
# プロジェクト初期化
npx playwright install
Playwright でのテスト実装
// login.spec.ts
import { test, expect } from '@playwright/test';
test.describe('ログイン機能', () => {
test('ユーザーがログインできる', async ({ page }) => {
// ログインページに移動
await page.goto('http://localhost:3000/login');
// ユーザー名を入力
await page.fill('input[name="username"]', 'testuser');
// パスワードを入力
await page.fill('input[name="password"]', 'password123');
// ログインボタンをクリック
await page.click('button:has-text("ログイン")');
// ダッシュボードページに遷移したか確認
await expect(page).toHaveURL('http://localhost:3000/dashboard');
// ウェルカムメッセージが表示されているか
await expect(page.locator('h1')).toContainText('ようこそ');
});
test('無効な認証情報でエラーが表示される', async ({ page }) => {
await page.goto('http://localhost:3000/login');
await page.fill('input[name="username"]', 'invalid');
await page.fill('input[name="password"]', 'wrong');
await page.click('button:has-text("ログイン")');
// エラーメッセージが表示される
await expect(page.locator('.error-message')).toBeVisible();
});
});
Playwright でのスクリーンショット・動画録画
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
// スクリーンショット設定
use: {
screenshot: 'only-on-failure', // 失敗時のみスクショ
video: 'retain-on-failure', // 失敗時のみ動画保存
},
// 複数ブラウザでテスト
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
});
React コンポーネントテスト(React Testing Library)
React Testing Library の哲学
React Testing Library は「ユーザーが使うように」テストすることを推奨しています。内部実装ではなく、ユーザーが見える UI をテストします。
// ❌ 良くないテスト(内部実装に依存)
test('state を確認', () => {
const { getByTestId } = render(<Counter />);
const stateValue = getByTestId('counter-state').textContent;
expect(stateValue).toBe('0');
});
// ✅ 良いテスト(ユーザー視点)
test('ボタンをクリックでカウント増加', () => {
render(<Counter />);
const button = screen.getByRole('button', { name: /increment/i });
const display = screen.getByText('0');
fireEvent.click(button);
expect(screen.getByText('1')).toBeInTheDocument();
});
テスト対象の優先順位
// テスト対象の優先順位(上から順に推奨)
// 1. getByRole - アクセシビリティ属性を使用
screen.getByRole('button', { name: /submit/i });
// 2. getByLabelText - ラベル経由
screen.getByLabelText('Username');
// 3. getByPlaceholderText - プレースホルダ経由
screen.getByPlaceholderText('Enter name');
// 4. getByText - テキスト内容経由
screen.getByText('Submit');
// 5. getByTestId - テスト用 ID(最終手段)
screen.getByTestId('submit-button');
テストカバレッジと品質指標
カバレッジの種類
| 種類 | 説明 | 目安 |
| ステートメント | 実行されたコード行数 | 80% |
| ブランチ | 条件分岐の網羅率 | 75% |
| 関数 | 定義された関数の実行率 | 80% |
| ライン | 物理的な行数の網羅率 | 80% |
# カバレッジレポート生成
npm run test -- --coverage
# 出力例
# ─────────────────────────────────────
# File | Stmts | Branch | Funcs
# ─────────────────────────────────────
# sum.js | 100% | 100% | 100%
# button.js | 85% | 75% | 90%
# ─────────────────────────────────────
CI/CD パイプラインでのテスト自動化
GitHub Actions でのテスト実行
# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Node.js セットアップ
uses: actions/setup-node@v3
with:
node-version: '18'
- name: 依存をインストール
run: npm ci
- name: ユニットテスト実行
run: npm run test
- name: ビルド確認
run: npm run build
- name: E2E テスト実行
run: npm run test:e2e
テストフレームワーク比較表
| 項目 | Jest | Vitest | Playwright |
| テスト種別 | ユニット・統合 | ユニット・統合 | E2E |
| 実行速度 | 中程度 | 高速 | 遅い(ブラウザ起動) |
| セットアップ | 簡単 | 簡単 | 中程度 |
| デバッグ性 | 優秀 | 優秀 | スクショ・動画 |
| クロスブラウザ | × | × | ○(3 ブラウザ) |
| HMR 対応 | × | ○ | × |
ベストプラクティス
テスト戦略
- テストは品質を保証するためのツール、すべてをカバーする必要はない
- ユニットテストで 70~80%、E2E で重要フロー のみカバー
- バグが発生した箇所からテストを追加する(テスト駆動開発)
- スナップショットテストは過度に使用しない
テスト作成のコツ
- AAA パターン:Arrange(準備)→ Act(実行)→ Assert(検証)
- 1 テスト 1 概念:複数の検証を避ける
- わかりやすい名前:テスト名は何をテストしているか明確に
- データ分離:テスト間でデータを共有しない
まとめ
フロントエンドテスティングは品質保証の基盤です。テストピラミッド構造で各レイヤーのテストを組み合わせることで、堅牢なアプリケーションが実現できます:
- Jest:フロントエンド標準。ユニット・統合テストに使用
- Vitest:Jest 互換で高速。Vite プロジェクトに推奨
- Playwright:E2E テストの最強ツール。実ブラウザでテスト
単位テスト・統合テスト・E2E テストをバランスよく実施し、自動化によりコード品質を継続的に保証しましょう。テストがあれば、安心してリファクタリングや機能追加ができます。
