jest の使い方

Delightful JavaScript Testing.

v30.3.0/週MITテスト
AI生成コンテンツ

この記事はAIによって生成されました。内容の正確性は保証されません。最新の情報は公式ドキュメントをご確認ください。

jest の使い方完全ガイド【v30対応】JavaScriptテストフレームワークの決定版

一言でいうと

Jest は、Meta(旧Facebook)が開発したJavaScript/TypeScript向けのオールインワンテストフレームワークです。テストランナー、アサーション、モック、カバレッジ計測がすべて内蔵されており、設定なし(Zero Config)ですぐにテストを始められます。


どんな時に使う?

  1. React/Node.jsアプリケーションのユニットテスト・統合テスト — コンポーネントやAPIハンドラのテストを、追加ライブラリなしで記述したい場合
  2. スナップショットテスト — UIコンポーネントやAPIレスポンスの出力が意図せず変更されていないかを自動検知したい場合
  3. モック・スタブを多用するテスト — 外部API呼び出しやDB接続などの依存を簡単に差し替えて、隔離されたテストを書きたい場合

インストール

# npm
npm install --save-dev jest

# yarn
yarn add --dev jest

# pnpm
pnpm add --save-dev jest

TypeScriptで使う場合

npm install --save-dev jest ts-jest @jest/globals @types/jest

jest.config.ts を作成します:

import type { Config } from 'jest';

const config: Config = {
  preset: 'ts-jest',
  testEnvironment: 'node',
};

export default config;

package.json にスクリプトを追加します:

{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage"
  }
}

基本的な使い方

最もシンプルなテストの例です。

src/sum.ts

export function sum(a: number, b: number): number {
  return a + b;
}

src/__tests__/sum.test.ts

import { sum } from '../sum';

describe('sum', () => {
  it('1 + 2 は 3 を返す', () => {
    expect(sum(1, 2)).toBe(3);
  });

  it('負の数同士の加算が正しく動作する', () => {
    expect(sum(-1, -2)).toBe(-3);
  });

  it('0を加算しても値が変わらない', () => {
    expect(sum(5, 0)).toBe(5);
  });
});

実行:

npm test

出力例:

 PASS  src/__tests__/sum.test.ts
  sum
    ✓ 1 + 2 は 3 を返す (2 ms)
    ✓ 負の数同士の加算が正しく動作する
    ✓ 0を加算しても値が変わらない (1 ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total

よく使うAPI

1. マッチャー(Matchers)

Jestのアサーションの中核です。expect() と組み合わせて使います。

describe('マッチャーの例', () => {
  // 厳密等価
  it('toBe — プリミティブ値の比較', () => {
    expect(1 + 1).toBe(2);
    expect('hello').toBe('hello');
    expect(true).toBe(true);
  });

  // ディープイコール(オブジェクト・配列の比較)
  it('toEqual — オブジェクトの深い比較', () => {
    const user = { name: 'Taro', age: 30 };
    expect(user).toEqual({ name: 'Taro', age: 30 });
  });

  // 部分一致
  it('toMatchObject — オブジェクトの部分一致', () => {
    const user = { name: 'Taro', age: 30, email: 'taro@example.com' };
    expect(user).toMatchObject({ name: 'Taro', age: 30 });
  });

  // 真偽値系
  it('Truthiness マッチャー', () => {
    expect(null).toBeNull();
    expect(undefined).toBeUndefined();
    expect('value').toBeDefined();
    expect(1).toBeTruthy();
    expect(0).toBeFalsy();
  });

  // 数値比較
  it('数値マッチャー', () => {
    expect(10).toBeGreaterThan(9);
    expect(10).toBeGreaterThanOrEqual(10);
    expect(10).toBeLessThan(11);
    expect(0.1 + 0.2).toBeCloseTo(0.3); // 浮動小数点対策
  });

  // 文字列・配列
  it('文字列と配列のマッチャー', () => {
    expect('Hello World').toMatch(/World/);
    expect(['apple', 'banana', 'cherry']).toContain('banana');
    expect([1, 2, 3]).toHaveLength(3);
  });

  // 例外
  it('toThrow — 例外の検証', () => {
    const throwError = () => {
      throw new Error('Something went wrong');
    };
    expect(throwError).toThrow('Something went wrong');
    expect(throwError).toThrow(Error);
  });
});

2. モック関数(jest.fn() / jest.mock()

外部依存を差し替えてテストを隔離します。

// --- 関数のモック ---
describe('jest.fn() の使い方', () => {
  it('コールバック関数をモックする', () => {
    const mockCallback = jest.fn((x: number) => x * 2);

    [1, 2, 3].forEach(mockCallback);

    // 呼び出し回数の検証
    expect(mockCallback).toHaveBeenCalledTimes(3);

    // 引数の検証
    expect(mockCallback).toHaveBeenCalledWith(1);
    expect(mockCallback).toHaveBeenCalledWith(2);
    expect(mockCallback).toHaveBeenCalledWith(3);

    // 戻り値の検証
    expect(mockCallback.mock.results[0].value).toBe(2);
    expect(mockCallback.mock.results[1].value).toBe(4);
  });

  it('戻り値を制御する', () => {
    const mock = jest.fn<(id: number) => string>();
    mock
      .mockReturnValueOnce('first')
      .mockReturnValueOnce('second')
      .mockReturnValue('default');

    expect(mock(1)).toBe('first');
    expect(mock(2)).toBe('second');
    expect(mock(3)).toBe('default');
    expect(mock(4)).toBe('default');
  });
});

モジュール全体のモック:

// src/api.ts
export async function fetchUser(id: number): Promise<{ name: string }> {
  const res = await fetch(`https://api.example.com/users/${id}`);
  return res.json();
}

// src/userService.ts
import { fetchUser } from './api';

export async function getUserName(id: number): Promise<string> {
  const user = await fetchUser(id);
  return user.name;
}
// src/__tests__/userService.test.ts
import { getUserName } from '../userService';
import { fetchUser } from '../api';

jest.mock('../api');

const mockedFetchUser = fetchUser as jest.MockedFunction<typeof fetchUser>;

describe('getUserName', () => {
  it('ユーザー名を返す', async () => {
    mockedFetchUser.mockResolvedValue({ name: 'Taro' });

    const name = await getUserName(1);

    expect(name).toBe('Taro');
    expect(mockedFetchUser).toHaveBeenCalledWith(1);
  });
});

3. 非同期テスト

// Promise / async-await
describe('非同期テスト', () => {
  // async/await(推奨)
  it('async/await パターン', async () => {
    const data = await Promise.resolve('hello');
    expect(data).toBe('hello');
  });

  // resolves / rejects マッチャー
  it('resolves マッチャー', async () => {
    await expect(Promise.resolve('done')).resolves.toBe('done');
  });

  it('rejects マッチャー', async () => {
    await expect(Promise.reject(new Error('fail'))).rejects.toThrow('fail');
  });
});

4. スナップショットテスト

UIやデータ構造の回帰テストに有効です。

interface UserProfile {
  id: number;
  name: string;
  email: string;
  createdAt: string;
}

function createUserProfile(name: string): UserProfile {
  return {
    id: 1,
    name,
    email: `${name.toLowerCase()}@example.com`,
    createdAt: '2024-01-01T00:00:00.000Z',
  };
}

describe('スナップショットテスト', () => {
  it('UserProfile の構造が変わっていないこと', () => {
    const profile = createUserProfile('Taro');
    expect(profile).toMatchSnapshot();
  });

  // インラインスナップショット(スナップショットがテストファイル内に保存される)
  it('インラインスナップショット', () => {
    const profile = createUserProfile('Hanako');
    expect(profile).toMatchInlineSnapshot(`
      {
        "createdAt": "2024-01-01T00:00:00.000Z",
        "email": "hanako@example.com",
        "id": 1,
        "name": "Hanako",
      }
    `);
  });
});

スナップショットを更新する場合:

npx jest --updateSnapshot
# または
npx jest -u

5. セットアップ・ティアダウン(ライフサイクル)

describe('ライフサイクルフック', () => {
  let db: { connected: boolean; data: string[] };

  // 全テストの前に1回だけ実行
  beforeAll(() => {
    db = { connected: true, data: [] };
    console.log('DB接続');
  });

  // 各テストの前に毎回実行
  beforeEach(() => {
    db.data = []; // テストごとにデータをリセット
  });

  // 各テストの後に毎回実行
  afterEach(() => {
    jest.restoreAllMocks(); // モックのリストア
  });

  // 全テストの後に1回だけ実行
  afterAll(() => {
    db.connected = false;
    console.log('DB切断');
  });

  it('データを追加できる', () => {
    db.data.push('item1');
    expect(db.data).toHaveLength(1);
  });

  it('データは毎回リセットされている', () => {
    expect(db.data).toHaveLength(0);
  });
});

類似パッケージとの比較

特徴JestVitestMocha
設定の手軽さ◎ Zero Config◎ Vite連携で即使える△ 別途設定が必要
アサーション内蔵内蔵(Jest互換)別途必要(Chai等)
モック内蔵内蔵(Jest互換)別途必要(Sinon等)
カバレッジ内蔵内蔵(c8/istanbul)別途必要(nyc等)
スナップショット◎ 内蔵◎ 内蔵△ プラグインが必要
実行速度◎ ESM/HMRで高速
ESMサポート△ 実験的◎ ネイティブ
エコシステム◎ 最大規模○ 急成長中◎ 歴史が長い
主な用途React/汎用Viteプロジェクト柔軟な構成が必要な場合

選定の目安:

  • Viteを使っている → Vitest が最適
  • React/既存の大規模プロジェクト → Jest が安定
  • 細かくツールを選びたい → Mocha + Chai + Sinon

注意点・Tips

1. ESMサポートについて

Jest は歴史的に CommonJS ベースで動作します。ESM(import/export)を使う場合は ts-jest や Babel の設定が必要です。

// jest.config.ts で ts-jest を使う場合
const config: Config = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  // ESMモジュールを変換対象に含める
  transformIgnorePatterns: [
    'node_modules/(?!(esm-only-package)/)',
  ],
};

注意(v30.x): Jest 30 では Node.js のネイティブ ESM サポートが改善されていますが、エコシステム全体の対応状況を確認してから導入してください。

2. テストの並列実行とパフォーマンス

Jest はデフォルトでテストファイルを並列実行します。CI環境でメモリ不足になる場合は以下を試してください。

# ワーカー数を制限
jest --maxWorkers=2

# 直列実行
jest --runInBand

3. jest.mock() の巻き上げ(Hoisting)

jest.mock() はファイルの先頭に自動的に巻き上げられます

比較記事