10日で覚えるJestDay 1: Jestの世界へようこそ
books.chapter 110日で覚えるJest

Day 1: Jestの世界へようこそ

今日学ぶこと

  • テストとは何か、なぜ必要なのか
  • テストピラミッドの概念
  • Jestとは何か、その特徴と利点
  • Jestのインストールとセットアップ
  • 最初のテストを書いて実行する

なぜテストが必要なのか

コードを書いたら、ブラウザで動かして確認する。ボタンを押して、フォームに入力して、結果を目で確認する——多くの開発者がこの「手動テスト」を繰り返しています。

しかし、この方法には限界があります。

flowchart LR
    subgraph Manual["手動テスト"]
        M1["コードを書く"]
        M2["ブラウザで確認"]
        M3["別の機能も確認"]
        M4["見落としが発生"]
    end
    subgraph Auto["自動テスト"]
        A1["コードを書く"]
        A2["テストを実行"]
        A3["全機能を自動チェック"]
        A4["問題を即座に検出"]
    end
    M1 --> M2 --> M3 --> M4
    A1 --> A2 --> A3 --> A4
    style Manual fill:#ef4444,color:#fff
    style Auto fill:#22c55e,color:#fff
手動テストの問題 自動テストによる解決
毎回同じ操作を繰り返す必要がある テストコマンド一発で全チェック
確認漏れが起きやすい すべてのテストケースを網羅的に実行
コード変更のたびに全体を確認する負担 変更が既存機能を壊していないか自動検証
チームメンバーによって確認手順が異なる テストコードが確認手順の「仕様書」になる

「10日で覚えるJavaScript」や「10日で覚えるReact」で書いたコードを思い出してください。機能を追加するたびに、以前の機能が壊れていないか心配になりませんでしたか?自動テストはその不安を解消してくれます。


テストピラミッド

ソフトウェアテストには複数のレベルがあり、それぞれ異なる目的を持っています。これを体系的に表したのがテストピラミッドです。

flowchart TB
    subgraph Pyramid["テストピラミッド"]
        E2E["E2Eテスト\n(少数・遅い・高コスト)"]
        INT["インテグレーションテスト\n(中程度)"]
        UNIT["ユニットテスト\n(多数・速い・低コスト)"]
    end
    E2E --> INT --> UNIT
    style E2E fill:#ef4444,color:#fff
    style INT fill:#f59e0b,color:#fff
    style UNIT fill:#22c55e,color:#fff
テストの種類 対象 速度
ユニットテスト 関数・コンポーネント単体 非常に速い add(2, 3)5 を返すか
インテグレーションテスト 複数モジュールの連携 中程度 APIからデータを取得して表示できるか
E2Eテスト アプリケーション全体 遅い ユーザーがログインして商品を購入できるか

ピラミッドの原則

  • ユニットテストを最も多く書く:高速で安定しており、問題の原因を特定しやすい
  • E2Eテストは重要なフローに絞る:実行が遅く、メンテナンスコストが高い
  • 各層が補完し合う:ユニットテストだけでは見つからない問題をインテグレーションテストやE2Eテストが検出する

このシリーズとの関連: 「10日で覚えるCypress」「10日で覚えるPlaywright」ではE2Eテストを学びました。この本で学ぶJestは、ピラミッドの土台となるユニットテストインテグレーションテストを担当するツールです。


Jestとは

Jestは、Meta(旧Facebook)が開発したJavaScriptテスティングフレームワークです。React、Vue、Angular、Node.jsなど、あらゆるJavaScriptプロジェクトで使用できます。

Jestの特徴

flowchart TB
    subgraph Jest["Jestの主要機能"]
        F1["ゼロ設定\nZero Config"]
        F2["高速実行\nParallel Testing"]
        F3["モック機能\nBuilt-in Mocking"]
        F4["スナップショット\nSnapshot Testing"]
        F5["カバレッジ\nCode Coverage"]
        F6["ウォッチモード\nWatch Mode"]
    end
    style Jest fill:#99425b,color:#fff
特徴 説明
ゼロ設定 インストールしてすぐ使える。複雑な設定ファイルは不要
高速実行 テストファイルを並列実行し、変更されたファイルのみ再テスト
オールインワン テストランナー、アサーション、モックが全て組み込まれている
スナップショットテスト UIの出力を記録し、意図しない変更を検出
コードカバレッジ 追加ツールなしでカバレッジレポートを生成
ウォッチモード ファイル変更を監視して関連テストを自動再実行

なぜJestを選ぶのか

JavaScriptのテスティングフレームワークは他にもあります(Mocha、Vitest、Jasmineなど)。Jestが広く使われている理由を見てみましょう。

フレームワーク 特徴 Jestとの違い
Mocha 柔軟性が高い アサーションやモックは別途インストールが必要
Vitest Viteベースで高速 Viteプロジェクト向け。APIはJest互換
Jasmine Jestの元になったフレームワーク 機能がJestより少ない

Jestは**バッテリー同梱(batteries included)**の思想で設計されています。テストランナー、アサーションライブラリ、モックライブラリ、カバレッジツールがすべて一つのパッケージに含まれているため、初心者でも迷わずに始められます。


Jestのインストールとセットアップ

前提条件

  • Node.js 18以上がインストールされていること
  • npm が使えること

「10日で覚えるJavaScript」のDay 1でNode.jsをインストール済みであれば準備完了です。

プロジェクトの作成

# プロジェクトディレクトリを作成
mkdir my-jest-project
cd my-jest-project

# package.json を初期化
npm init -y

# Jestをインストール
npm install --save-dev jest

package.json の設定

package.jsonscripts にテスト実行コマンドを追加します。

{
  "name": "my-jest-project",
  "version": "1.0.0",
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage"
  },
  "devDependencies": {
    "jest": "^30.0.0"
  }
}

TypeScript環境のセットアップ

TypeScriptを使う場合は、追加のパッケージが必要です。

# TypeScript関連パッケージをインストール
npm install --save-dev typescript ts-jest @types/jest

# TypeScript設定ファイルを生成
npx tsc --init

Jest設定ファイル(jest.config.js)を作成します。

/** @type {import('jest').Config} */
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
};

JavaScript版の場合jest.config.js は不要です。Jestはデフォルトで .test.js.spec.js ファイルを自動検出します。


最初のテストを書こう

テスト対象の関数を作成

まず、テストする関数を作りましょう。math.js というファイルを作成します。

// math.js
function add(a, b) {
  return a + b;
}

function subtract(a, b) {
  return a - b;
}

function multiply(a, b) {
  return a * b;
}

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

module.exports = { add, subtract, multiply, divide };

TypeScript版:

// math.ts
export function add(a: number, b: number): number {
  return a + b;
}

export function subtract(a: number, b: number): number {
  return a - b;
}

export function multiply(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;
}

テストファイルを作成

次に、テストファイルを作成します。Jestは以下のパターンのファイルを自動的にテストとして認識します。

パターン
*.test.js / *.test.ts math.test.js
*.spec.js / *.spec.ts math.spec.js
__tests__/ ディレクトリ内のファイル __tests__/math.js

math.test.js を作成しましょう。

// math.test.js
const { add, subtract, multiply, divide } = require('./math');

test('adds 1 + 2 to equal 3', () => {
  expect(add(1, 2)).toBe(3);
});

test('subtracts 5 - 3 to equal 2', () => {
  expect(subtract(5, 3)).toBe(2);
});

test('multiplies 3 * 4 to equal 12', () => {
  expect(multiply(3, 4)).toBe(12);
});

test('divides 10 / 2 to equal 5', () => {
  expect(divide(10, 2)).toBe(5);
});

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

TypeScript版:

// math.test.ts
import { add, subtract, multiply, divide } from './math';

test('adds 1 + 2 to equal 3', () => {
  expect(add(1, 2)).toBe(3);
});

test('subtracts 5 - 3 to equal 2', () => {
  expect(subtract(5, 3)).toBe(2);
});

test('multiplies 3 * 4 to equal 12', () => {
  expect(multiply(3, 4)).toBe(12);
});

test('divides 10 / 2 to equal 5', () => {
  expect(divide(10, 2)).toBe(5);
});

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

テストを実行

npm test

実行結果:

PASS  ./math.test.js
  ✓ adds 1 + 2 to equal 3 (2 ms)
  ✓ subtracts 5 - 3 to equal 2
  ✓ multiplies 3 * 4 to equal 12
  ✓ divides 10 / 2 to equal 5
  ✓ throws error when dividing by zero (1 ms)

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        0.5 s

すべてのテストが緑色(PASS)で表示されれば成功です。


テストコードの解説

最初のテストコードを詳しく見てみましょう。

test('adds 1 + 2 to equal 3', () => {
  expect(add(1, 2)).toBe(3);
});
flowchart LR
    subgraph TestAnatomy["テストの構造"]
        TEST["test(name, fn)\nテスト関数"]
        EXPECT["expect(value)\n実際の値"]
        MATCHER["toBe(expected)\nマッチャー"]
    end
    TEST --> EXPECT --> MATCHER
    style TEST fill:#3b82f6,color:#fff
    style EXPECT fill:#8b5cf6,color:#fff
    style MATCHER fill:#22c55e,color:#fff
要素 説明
test(name, fn) テストケースを定義する。name はテストの説明 test('adds 1 + 2 to equal 3', ...)
expect(value) テスト対象の値を指定する expect(add(1, 2))
.toBe(expected) 期待値と一致するか検証する(マッチャー) .toBe(3)

テストが失敗するとどうなるか

試しにテストを壊してみましょう。add 関数の実装を変更します。

function add(a, b) {
  return a - b; // intentional bug
}

テストを実行すると:

FAIL  ./math.test.js
  ✗ adds 1 + 2 to equal 3 (3 ms)

  ● adds 1 + 2 to equal 3

    expect(received).toBe(expected) // Object.is equality

    Expected: 3
    Received: -1

      1 | const { add } = require('./math');
      2 | test('adds 1 + 2 to equal 3', () => {
    > 3 |   expect(add(1, 2)).toBe(3);
        |                     ^
      4 | });

Jestは**何が期待されていたか(Expected: 3)実際の結果(Received: -1)**を明確に示してくれます。さらに、問題が起きたコードの行をハイライトしてくれるので、バグの原因を素早く特定できます。


ウォッチモード

開発中は、ファイルを保存するたびにテストが自動的に再実行されるウォッチモードが便利です。

npm run test:watch

ウォッチモードでは、以下の操作が可能です。

キー 動作
a すべてのテストを実行
f 失敗したテストのみ再実行
p ファイル名でフィルタ
t テスト名でフィルタ
q ウォッチモードを終了
flowchart TB
    subgraph Watch["ウォッチモードの動作"]
        EDIT["ファイルを編集・保存"]
        DETECT["Jestが変更を検知"]
        RUN["関連テストを自動実行"]
        RESULT["結果を即座に表示"]
    end
    EDIT --> DETECT --> RUN --> RESULT --> EDIT
    style Watch fill:#3b82f6,color:#fff

プロジェクト構成のベストプラクティス

テストファイルの配置には主に2つのパターンがあります。

パターン1: テストファイルを隣に置く(推奨)

src/
├── math.js
├── math.test.js
├── utils/
│   ├── format.js
│   └── format.test.js
└── components/
    ├── Button.jsx
    └── Button.test.jsx

パターン2: tests ディレクトリにまとめる

src/
├── math.js
├── utils/
│   └── format.js
├── components/
│   └── Button.jsx
└── __tests__/
    ├── math.test.js
    ├── format.test.js
    └── Button.test.jsx
パターン メリット デメリット
隣に置く テスト対象とテストの対応が明確 ディレクトリ内のファイル数が増える
tests テストが一箇所にまとまる テスト対象との対応がわかりにくい

多くのプロジェクトではパターン1が採用されています。テスト対象のファイルのすぐ隣にテストがあることで、テストの存在に気づきやすく、メンテナンスもしやすくなります。


まとめ

概念 説明
自動テスト コードの動作を自動的に検証する仕組み
テストピラミッド Unit → Integration → E2E の3層構造
Jest Meta開発のオールインワンテスティングフレームワーク
test() テストケースを定義する関数
expect().toBe() 値が期待通りかを検証する
ウォッチモード ファイル変更時にテストを自動再実行

重要ポイント

  1. 自動テストはコードの品質を守る安全ネット
  2. Jestはインストールするだけで、設定なしですぐに使える
  3. test() + expect() + マッチャーがテストの基本構造
  4. ウォッチモードで開発効率を大幅に向上できる

練習問題

問題1: 基本

以下の greet 関数のテストを書いてください。

function greet(name) {
  return `Hello, ${name}!`;
}

期待する動作:

  • greet('Alice')'Hello, Alice!' を返す
  • greet('World')'Hello, World!' を返す

問題2: 応用

以下の isEven 関数のテストを書いてください。複数のケースをテストしましょう。

function isEven(num) {
  return num % 2 === 0;
}

チャレンジ問題

以下の clamp 関数のテストを書いてください。境界値(min、max、範囲内)のすべてのケースをカバーしましょう。

function clamp(value, min, max) {
  return Math.min(Math.max(value, min), max);
}

参考リンク


次回予告: Day 2では「テストの構造と基本パターン」について学びます。describe によるテストのグループ化、beforeEach / afterEach によるセットアップ、AAA(Arrange-Act-Assert)パターンなど、保守しやすいテストを書くための技法をマスターしましょう!