T-CREATOR

Jest が得意/不得意な領域を整理:単体・契約・統合・E2E の住み分け最新指針

Jest が得意/不得意な領域を整理:単体・契約・統合・E2E の住み分け最新指針

JavaScript のテストフレームワークとして圧倒的な人気を誇る Jest。しかし、すべてのテストを Jest で書くべきでしょうか?実は、テストの種類によって Jest が得意な領域と不得意な領域があります。

本記事では、単体テスト・契約テスト・統合テスト・E2E テストという 4 つのテスト領域において、Jest がどこまで適しているのか、そしてどのような場面で他のツールを選択すべきかを整理します。適切なツールを選ぶことで、テストの保守性と信頼性が格段に向上するでしょう。

背景

テストピラミッドとテストの種類

ソフトウェアテストには、検証する範囲や目的に応じて複数の種類があります。代表的なものが「テストピラミッド」として知られる階層構造です。

このピラミッドは、下層から「単体テスト(Unit Test)」「統合テスト(Integration Test)」「E2E テスト(End-to-End Test)」という順に積み重なっており、下層ほど数が多く実行速度が速い、上層ほど数が少なく実行に時間がかかるという特徴があります。さらに近年では「契約テスト(Contract Test)」という概念も注目されています。

以下の図は、各テスト種別とその特徴を示したものです。

mermaidflowchart TB
    subgraph pyramid["テストピラミッド"]
        e2e["E2E テスト<br/>実行時間:遅い<br/>コスト:高い<br/>信頼性:最高"]
        integration["統合テスト<br/>実行時間:中程度<br/>コスト:中程度<br/>信頼性:高い"]
        unit["単体テスト<br/>実行時間:速い<br/>コスト:低い<br/>信頼性:中程度"]
    end

    contract["契約テスト<br/>マイクロサービス間の<br/>インターフェース検証"]

    e2e --> integration
    integration --> unit
    contract -.->|補完| integration

各テスト種別が検証する範囲を理解することで、適切なツール選択が可能になります。

Jest の特徴と設計思想

Jest は Facebook(現 Meta)が開発した JavaScript テストフレームワークで、以下のような特徴を持っています。

Jest の主要機能

#機能説明
1ゼロコンフィグ最小限の設定でテストを開始可能
2スナップショットテストUI コンポーネントの変更検出
3モック機能依存関係を簡単に置き換え
4並列実行テストを高速に実行
5カバレッジレポートコードカバレッジを自動計測

Jest は特に Node.js 環境での JavaScript/TypeScript コードのテスト に最適化されています。React や Vue.js などのフロントエンドフレームワークとの相性も良く、多くのプロジェクトで採用されてきました。

しかし、Jest の設計は主に 単体テストと一部の統合テスト を想定しており、すべてのテストシナリオに適しているわけではありません。

課題

Jest ですべてのテストを書く際の問題点

多くの開発チームが Jest の便利さゆえに、すべてのテスト種別を Jest で書こうとしてしまいます。しかし、これにはいくつかの課題があります。

以下の図は、各テスト領域における Jest の適合度を示しています。

mermaidflowchart LR
    subgraph tests["テスト種別と Jest の適合度"]
        unit["単体テスト<br/>★★★★★<br/>最適"]
        integration["統合テスト<br/>★★★☆☆<br/>条件付き可"]
        contract["契約テスト<br/>★★☆☆☆<br/>不向き"]
        e2e["E2E テスト<br/>★☆☆☆☆<br/>不適切"]
    end

    jest["Jest"] --> unit
    jest -.->|工夫が必要| integration
    jest -.->|専用ツール推奨| contract
    jest -.->|別ツール必須| e2e

この図からわかるように、Jest は単体テストには最適ですが、テストの種類が上位になるほど適合度が下がっていきます。

領域ごとの具体的な課題

単体テストにおける課題

Jest は単体テストに最適化されていますが、以下のような制約があります。

#課題詳細
1ブラウザ API の制限jsdom は完全なブラウザ環境ではない
2非同期処理の複雑化タイマーのモックが複雑になる場合がある
3グローバル状態の影響テスト間の独立性を保つ工夫が必要

特に、window.locationlocalStorage などのブラウザ固有の API を使うコードでは、jsdom の制限により期待通りの動作をしないことがあります。

統合テストにおける課題

統合テストでは、複数のモジュールや外部サービスとの連携を検証します。Jest でも可能ですが、以下の課題があります。

typescript// データベース接続を伴う統合テストの例
// Jest だけでは不十分なケース
typescriptimport { createUser } from './userService';
import { database } from './database';

describe('User Service Integration', () => {
  // 課題1: データベースのセットアップとクリーンアップが煩雑
  beforeEach(async () => {
    await database.connect();
    await database.migrate();
  });

  afterEach(async () => {
    await database.cleanup();
    await database.disconnect();
  });

このように、外部リソースの管理が複雑になります。

typescript  // 課題2: 実際のデータベースを使うと遅くなる
  test('should create user in database', async () => {
    const user = await createUser({
      name: 'Test User',
      email: 'test@example.com'
    });

    expect(user.id).toBeDefined();
    expect(user.name).toBe('Test User');
  });
});

統合テストでは実行時間が長くなりがちで、Jest の高速性というメリットが薄れます。

契約テストにおける課題

契約テストは、マイクロサービス間の API インターフェースが仕様通りに動作するかを検証します。Jest では以下の制約があります。

#課題理由
1契約の定義が困難スキーマ定義の標準化がない
2プロバイダー検証の欠如双方向の検証機能がない
3契約の共有が手動契約ファイルの管理が煩雑

契約テストには Pact のような専用ツールが持つ「コンシューマー駆動契約」の仕組みが不可欠です。

E2E テストにおける課題

E2E テストでは、実際のブラウザを使ってユーザーの操作を再現します。Jest の主な課題は以下の通りです。

typescript// Jest + Puppeteer の組み合わせでも限界がある
typescriptimport puppeteer from 'puppeteer';

describe('Login Flow E2E', () => {
  let browser;
  let page;

  beforeAll(async () => {
    // 課題1: ブラウザ起動の管理が煩雑
    browser = await puppeteer.launch();
    page = await browser.newPage();
  });

ブラウザの起動やページ管理を手動で行う必要があり、コードが冗長になります。

typescripttest('should login successfully', async () => {
  await page.goto('http://localhost:3000/login');

  // 課題2: 要素の待機処理を明示的に書く必要がある
  await page.waitForSelector('#email');
  await page.type('#email', 'user@example.com');
  await page.type('#password', 'password123');

  // 課題3: エラー時のデバッグが困難
  await page.click('button[type="submit"]');
  await page.waitForNavigation();

  const url = page.url();
  expect(url).toBe('http://localhost:3000/dashboard');
});

E2E テスト専用のフレームワークと比べて、デバッグ機能やリトライ機能が貧弱です。

typescript  afterAll(async () => {
    await browser.close();
  });
});

これらの課題から、各テスト領域に適したツールを選択する必要性が見えてきます。

解決策

テスト領域ごとの最適なツール選択

各テスト領域の特性を理解し、Jest の得意領域と他のツールが得意な領域を住み分けることが重要です。以下の図は、推奨されるツールの住み分けを示しています。

mermaidflowchart TB
    subgraph unit_area["単体テスト領域"]
        jest_unit["Jest<br/>★ 最適解<br/>高速・シンプル・豊富な機能"]
        vitest["Vitest<br/>Vite プロジェクト向け"]
    end

    subgraph integration_area["統合テスト領域"]
        jest_int["Jest + Supertest<br/>API 統合テスト"]
        testcontainers["Jest + Testcontainers<br/>DB 統合テスト"]
    end

    subgraph contract_area["契約テスト領域"]
        pact["Pact<br/>★ 最適解<br/>双方向契約検証"]
        openapi["OpenAPI Validator<br/>スキーマ検証"]
    end

    subgraph e2e_area["E2E テスト領域"]
        playwright["Playwright<br/>★ 最適解<br/>複数ブラウザ対応"]
        cypress["Cypress<br/>DX 重視"]
    end

    unit_area --> integration_area
    integration_area --> contract_area
    integration_area --> e2e_area

この図のように、各領域で最も効果を発揮するツールを選択することで、テストの品質と保守性が向上します。

単体テストでの Jest 活用法

単体テストは Jest が最も得意とする領域です。以下のような実践方法を推奨します。

関数の単体テスト

純粋関数のテストは Jest の最も得意とするところです。

typescript// テスト対象の関数
// src/utils/calculator.ts
typescriptexport function add(a: number, b: number): number {
  return a + b;
}

export function divide(a: number, b: number): number {
  if (b === 0) {
    throw new Error('Division by zero');
  }
  return a / b;
}

この関数に対するテストは非常にシンプルに書けます。

typescript// src/utils/calculator.test.ts
typescriptimport { add, divide } from './calculator';

describe('Calculator Utils', () => {
  describe('add', () => {
    test('should add two positive numbers', () => {
      expect(add(2, 3)).toBe(5);
    });

    test('should add negative numbers', () => {
      expect(add(-2, -3)).toBe(-5);
    });
  });
typescript  describe('divide', () => {
    test('should divide two numbers', () => {
      expect(divide(10, 2)).toBe(5);
    });

    test('should throw error when dividing by zero', () => {
      expect(() => divide(10, 0))
        .toThrow('Division by zero');
    });
  });
});

このように、Jest の豊富なマッチャーを活用して直感的なテストが書けます。

React コンポーネントの単体テスト

React コンポーネントのテストも Jest + React Testing Library の組み合わせが最適です。

typescript// src/components/Button.tsx
typescriptimport React from 'react';

interface ButtonProps {
  onClick: () => void;
  children: React.ReactNode;
  disabled?: boolean;
}

export const Button: React.FC<ButtonProps> = ({
  onClick,
  children,
  disabled = false,
}) => {
  return (
    <button onClick={onClick} disabled={disabled}>
      {children}
    </button>
  );
};

コンポーネントのテストでは、ユーザーの視点でのテストを意識します。

typescript// src/components/Button.test.tsx
typescriptimport { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';

describe('Button Component', () => {
  test('should render children text', () => {
    render(<Button onClick={() => {}}>Click me</Button>);
    expect(screen.getByText('Click me')).toBeInTheDocument();
  });
typescripttest('should call onClick when clicked', () => {
  const handleClick = jest.fn();
  render(<Button onClick={handleClick}>Click me</Button>);

  fireEvent.click(screen.getByText('Click me'));
  expect(handleClick).toHaveBeenCalledTimes(1);
});
typescript  test('should not call onClick when disabled', () => {
    const handleClick = jest.fn();
    render(
      <Button onClick={handleClick} disabled>
        Click me
      </Button>
    );

    fireEvent.click(screen.getByText('Click me'));
    expect(handleClick).not.toHaveBeenCalled();
  });
});

Jest のモック機能を使うことで、イベントハンドラーの呼び出しを簡単に検証できます。

統合テストでの Jest 活用法(条件付き)

統合テストでは、Jest を使う場合は適切なライブラリとの組み合わせが重要です。

API 統合テストでの活用

Express などの API サーバーのテストには、Jest + Supertest の組み合わせが有効です。

typescript// src/app.ts
typescriptimport express from 'express';

export const app = express();

app.use(express.json());

app.get('/api/users/:id', (req, res) => {
  const { id } = req.params;
  // 実際はデータベースから取得
  res.json({ id, name: 'Test User' });
});

app.post('/api/users', (req, res) => {
  const { name, email } = req.body;
  // バリデーション
  if (!name || !email) {
    return res.status(400).json({
      error: 'Name and email are required',
    });
  }
  res.status(201).json({ id: '123', name, email });
});

API のテストでは、HTTP リクエストとレスポンスを検証します。

typescript// src/app.test.ts
typescriptimport request from 'supertest';
import { app } from './app';

describe('User API Integration', () => {
  describe('GET /api/users/:id', () => {
    test('should return user data', async () => {
      const response = await request(app)
        .get('/api/users/123')
        .expect(200);

      expect(response.body).toEqual({
        id: '123',
        name: 'Test User'
      });
    });
  });
typescript  describe('POST /api/users', () => {
    test('should create new user', async () => {
      const response = await request(app)
        .post('/api/users')
        .send({ name: 'New User', email: 'new@example.com' })
        .expect(201);

      expect(response.body.id).toBeDefined();
      expect(response.body.name).toBe('New User');
    });
typescript    test('should return 400 for invalid data', async () => {
      const response = await request(app)
        .post('/api/users')
        .send({ name: 'New User' })
        .expect(400);

      expect(response.body.error).toBeDefined();
    });
  });
});

Supertest を使うことで、サーバーを起動せずに API テストを実行できます。

データベース統合テストでの活用

データベースを含む統合テストでは、Testcontainers を使うと環境の再現性が高まります。

typescript// jest.config.js の設定
javascriptmodule.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  // 統合テストは時間がかかるのでタイムアウトを延長
  testTimeout: 30000,
  // 統合テストは並列実行を制限
  maxWorkers: 2,
};

設定を調整することで、統合テストの実行環境を最適化できます。

typescript// src/database.integration.test.ts
typescriptimport { GenericContainer, StartedTestContainer } from 'testcontainers';
import { Client } from 'pg';

describe('Database Integration', () => {
  let container: StartedTestContainer;
  let client: Client;

  // テスト前にコンテナを起動
  beforeAll(async () => {
    container = await new GenericContainer('postgres:15')
      .withEnvironment({ POSTGRES_PASSWORD: 'test' })
      .withExposedPorts(5432)
      .start();
typescript    // データベースクライアントを接続
    client = new Client({
      host: container.getHost(),
      port: container.getMappedPort(5432),
      user: 'postgres',
      password: 'test',
      database: 'postgres',
    });
    await client.connect();
  });
typescripttest('should insert and retrieve data', async () => {
  await client.query(`
      CREATE TABLE users (
        id SERIAL PRIMARY KEY,
        name VARCHAR(100)
      )
    `);

  await client.query(
    'INSERT INTO users (name) VALUES ($1)',
    ['Test User']
  );

  const result = await client.query('SELECT * FROM users');
  expect(result.rows).toHaveLength(1);
  expect(result.rows[0].name).toBe('Test User');
});
typescript  // テスト後にクリーンアップ
  afterAll(async () => {
    await client.end();
    await container.stop();
  });
});

Testcontainers を使うことで、実際のデータベースを使った信頼性の高いテストが可能になります。

契約テストでは Pact を使用

契約テストには、Jest ではなく Pact のような専用ツールを使うべきです。

Pact による契約テストの例

コンシューマー側(API を呼び出す側)のテストを作成します。

typescript// consumer.pact.test.ts
typescriptimport { PactV3, MatchersV3 } from '@pact-foundation/pact';
import { fetchUser } from './userClient';

const { like, integer } = MatchersV3;

const provider = new PactV3({
  consumer: 'UserWebApp',
  provider: 'UserAPI',
});

Pact では、期待する API のレスポンス形式を定義します。

typescriptdescribe('User API Contract', () => {
  test('should get user by id', async () => {
    await provider
      .given('user with id 123 exists')
      .uponReceiving('a request for user 123')
      .withRequest({
        method: 'GET',
        path: '/api/users/123',
      })
      .willRespondWith({
        status: 200,
        headers: { 'Content-Type': 'application/json' },
        body: like({
          id: integer(123),
          name: 'Test User',
          email: 'test@example.com',
        }),
      })
      .executeTest(async (mockServer) => {
        // モックサーバーに対してリクエストを実行
        const user = await fetchUser(mockServer.url, 123);
        expect(user.id).toBe(123);
      });
  });
});

Pact は契約ファイルを生成し、プロバイダー側でも同じ契約を検証できます。これにより、API の仕様変更による問題を早期に発見できます。

E2E テストでは Playwright を使用

E2E テストには、Playwright のような専用フレームワークを使うことを強く推奨します。

Playwright による E2E テストの例

Playwright は、Jest よりも強力なブラウザ自動化機能を提供します。

typescript// playwright.config.ts
typescriptimport { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './e2e',
  // 並列実行の設定
  fullyParallel: true,
  // リトライ設定
  retries: process.env.CI ? 2 : 0,
  // 複数ブラウザでのテスト
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
  ],
});

設定ファイルで、複数ブラウザでのテストやリトライ戦略を簡単に定義できます。

typescript// e2e/login.spec.ts
typescriptimport { test, expect } from '@playwright/test';

test.describe('Login Flow', () => {
  test('should login successfully', async ({ page }) => {
    // ページに移動
    await page.goto('http://localhost:3000/login');

    // フォームに入力(自動待機機能付き)
    await page.fill('#email', 'user@example.com');
    await page.fill('#password', 'password123');
    await page.click('button[type="submit"]');

Playwright は要素が表示されるまで自動で待機するため、明示的な待機処理が不要です。

typescript    // ナビゲーションを待つ
    await page.waitForURL('**/dashboard');

    // アサーション
    await expect(page).toHaveURL(/.*dashboard/);
    await expect(page.locator('h1')).toContainText('Dashboard');
  });
typescript  test('should show error for invalid credentials', async ({ page }) => {
    await page.goto('http://localhost:3000/login');

    await page.fill('#email', 'wrong@example.com');
    await page.fill('#password', 'wrongpassword');
    await page.click('button[type="submit"]');

    // エラーメッセージの表示を確認
    await expect(page.locator('.error-message'))
      .toBeVisible();
    await expect(page.locator('.error-message'))
      .toContainText('Invalid credentials');
  });
});

Playwright は、スクリーンショット・動画録画・トレース機能など、デバッグに役立つ機能を標準で提供しています。

ツール選択の判断基準

各テスト領域でツールを選択する際の判断基準を表にまとめます。

#テスト種別推奨ツールJest の使用判断ポイント
1単体テストJest / Vitest★★★★★ 最適純粋関数、コンポーネント、モジュール単位
2API 統合テストJest + Supertest★★★★☆ 推奨HTTP API のエンドポイント検証
3DB 統合テストJest + Testcontainers★★★☆☆ 可実行時間が許容できる場合のみ
4契約テストPact☆☆☆☆☆ 不適マイクロサービス間の契約検証
5E2E テストPlaywright / Cypress☆☆☆☆☆ 不適ブラウザ操作を伴う統合検証

この表を参考に、プロジェクトの要件に応じて最適なツールを選択してください。

具体例

実践プロジェクトでのツール構成

実際のプロジェクトでは、複数のテストツールを組み合わせて使用します。以下は、Next.js を使った Web アプリケーションでの推奨構成です。

以下の図は、プロジェクト全体でのテストツールの配置を示しています。

mermaidflowchart TB
    subgraph app["Next.js アプリケーション"]
        components["React Components"]
        api["API Routes"]
        utils["Utils/Helpers"]
        services["Services"]
    end

    subgraph test_tools["テストツール構成"]
        jest["Jest<br/>単体テスト<br/>統合テスト(一部)"]
        pact["Pact<br/>契約テスト"]
        playwright["Playwright<br/>E2E テスト"]
    end

    components --> jest
    utils --> jest
    api --> jest
    services --> jest
    services --> pact
    app --> playwright

このように、各層に適したテストツールを配置することで、効率的なテスト戦略が実現できます。

package.json での設定例

複数のテストツールを使う場合の package.json の設定例です。

json{
  "name": "my-nextjs-app",
  "version": "1.0.0",
  "scripts": {
    "test": "yarn test:unit && yarn test:integration",
    "test:unit": "jest --testPathPattern=\\.test\\.(ts|tsx)$",
    "test:integration": "jest --testPathPattern=\\.integration\\.test\\.(ts|tsx)$",
    "test:contract": "jest --testPathPattern=\\.pact\\.test\\.(ts|tsx)$",
    "test:e2e": "playwright test",
    "test:all": "yarn test && yarn test:contract && yarn test:e2e",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage"
  }
}

スクリプトを分けることで、必要なテストだけを実行できます。

json{
  "devDependencies": {
    "@jest/globals": "^29.7.0",
    "@pact-foundation/pact": "^12.1.0",
    "@playwright/test": "^1.40.0",
    "@testing-library/jest-dom": "^6.1.5",
    "@testing-library/react": "^14.1.2",
    "@types/jest": "^29.5.11",
    "@types/supertest": "^6.0.2",
    "jest": "^29.7.0",
    "jest-environment-jsdom": "^29.7.0",
    "supertest": "^6.3.3",
    "testcontainers": "^10.4.0",
    "ts-jest": "^29.1.1"
  }
}

各テストツールの依存関係を適切に管理します。

ディレクトリ構成の例

テストファイルを種別ごとに整理することで、保守性が向上します。

bashmy-nextjs-app/
├── src/
│   ├── components/
│   │   ├── Button.tsx
│   │   └── Button.test.tsx          # 単体テスト
│   ├── utils/
│   │   ├── calculator.ts
│   │   └── calculator.test.ts       # 単体テスト
│   ├── services/
│   │   ├── userService.ts
│   │   ├── userService.test.ts      # 単体テスト
│   │   └── userService.integration.test.ts  # 統合テスト
│   └── app/
│       └── api/
│           ├── users/
│           │   └── route.ts
│           └── users.integration.test.ts    # API 統合テスト
├── tests/
│   ├── contract/
│   │   └── userAPI.pact.test.ts     # 契約テスト
│   └── e2e/
│       ├── login.spec.ts            # E2E テスト
│       └── dashboard.spec.ts        # E2E テスト
├── jest.config.js
├── playwright.config.ts
└── package.json

ファイル名の規則を統一することで、テストの種類が一目でわかります。

Jest の設定ファイル例

単体テストと統合テストで異なる設定を使う場合の jest.config.js の例です。

javascript// jest.config.js
javascriptmodule.exports = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',

  // カバレッジの設定
  collectCoverageFrom: [
    'src/**/*.{ts,tsx}',
    '!src/**/*.test.{ts,tsx}',
    '!src/**/*.integration.test.{ts,tsx}',
  ],
javascript  // モジュール名のマッピング
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
    '\\.(css|less|scss|sass)$': 'identity-obj-proxy',
  },
javascript  // セットアップファイル
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],

  // テストファイルのパターン
  testMatch: [
    '**/__tests__/**/*.(test|spec).(ts|tsx|js)',
    '**/*.(test|spec).(ts|tsx|js)',
  ],
javascript  // タイムアウト設定(統合テスト用に延長)
  testTimeout: 10000,

  // グローバル設定
  globals: {
    'ts-jest': {
      tsconfig: {
        jsx: 'react',
      },
    },
  },
};

この設定により、Jest でのテスト実行環境を最適化できます。

実際のテスト実行フロー

CI/CD パイプラインでのテスト実行順序の例です。

mermaidflowchart LR
    subgraph ci["CI/CD パイプライン"]
        direction TB

        commit["コミット"] --> unit["単体テスト<br/>Jest<br/>所要時間: 30秒"]
        unit --> integration["統合テスト<br/>Jest + Supertest<br/>所要時間: 2分"]
        integration --> contract["契約テスト<br/>Pact<br/>所要時間: 1分"]
        contract --> e2e["E2E テスト<br/>Playwright<br/>所要時間: 5分"]

        e2e --> deploy["デプロイ"]
    end

    unit -.->|失敗| fail["ビルド失敗"]
    integration -.->|失敗| fail
    contract -.->|失敗| fail
    e2e -.->|失敗| fail

テストを段階的に実行することで、問題を早期に発見し、フィードバックサイクルを短縮できます。高速な単体テストから順に実行し、遅い E2E テストは最後に実行することで、効率的なパイプラインを構築できます。

エラー処理とデバッグのベストプラクティス

各テストツールでのエラー処理の違いを理解しておくことが重要です。

Jest でのエラーハンドリング

Jest では、エラーメッセージが詳細に表示されます。

typescript// エラーが発生するテストの例
typescripttest('should handle async errors', async () => {
  const fetchData = async () => {
    throw new Error('Network error: Failed to fetch data');
  };

  // エラーコード: Error
  // エラーメッセージ: Network error: Failed to fetch data
  await expect(fetchData()).rejects.toThrow(
    'Network error'
  );
});

エラーメッセージには必ず具体的な情報を含めることで、検索性が向上します。

Playwright でのエラーハンドリング

Playwright では、スクリーンショットやトレースを活用します。

typescript// e2e/error-handling.spec.ts
typescriptimport { test, expect } from '@playwright/test';

test('should handle network errors gracefully', async ({
  page,
}) => {
  // ネットワークエラーをシミュレート
  await page.route('**/api/users', (route) => {
    route.abort('failed');
  });

  await page.goto('http://localhost:3000/users');

  // エラー UI が表示されることを確認
  await expect(page.locator('.error-banner')).toContainText(
    'Failed to load users'
  );
});

Playwright は失敗時に自動でスクリーンショットを保存するため、デバッグが容易です。

エラー情報の整理テーブル

よくあるエラーとその解決方法を整理します。

#エラーコードエラーメッセージ発生条件解決方法
1ECONNREFUSEDError: connect ECONNREFUSEDAPI サーバーが起動していないテスト前にサーバーを起動
2TypeErrorTypeError: Cannot read property 'x' of undefinedオブジェクトが undefinednull チェックを追加
3TimeoutErrorTimeoutError: Waiting for selector要素が見つからないセレクターを修正、待機時間を延長
4NetworkErrorNetworkError: Failed to fetchネットワーク接続の問題モックを使用、リトライを実装

これらのエラーパターンを理解しておくことで、トラブルシューティングが迅速になります。

まとめ

Jest は JavaScript のテストフレームワークとして非常に優れていますが、すべてのテスト領域に適しているわけではありません。本記事で整理した通り、テストの種類によって最適なツールを選択することが重要です。

Jest が最適な領域は、単体テストと一部の統合テスト(特に API テスト)です。高速で直感的な API、豊富なモック機能、優れた開発者体験により、これらの領域では Jest が圧倒的に優れています。

Jest が不向きな領域は、契約テストと E2E テストです。契約テストには Pact のような双方向検証機能を持つ専用ツールが、E2E テストには Playwright や Cypress のようなブラウザ自動化に特化したツールが適しています。

適切なツールを選択することで、テストの実行速度・保守性・信頼性のすべてが向上します。Jest の得意分野では Jest を使い、それ以外の領域では専用ツールを活用する。この「適材適所」の考え方が、効果的なテスト戦略の基本となるでしょう。

プロジェクトの規模や要件に応じて、本記事で紹介したツール構成を参考に、最適なテスト環境を構築してください。

関連リンク