はじめに#
テスト駆動開発(TDD)を始めるには、まず信頼できるテスト環境を構築することが不可欠です。JavaScriptの世界において、JestはMeta(旧Facebook)が開発したテスティングフレームワークであり、シンプルなセットアップ、高速な実行、豊富な機能により、最も広く使われているテストツールの一つです。
本記事では、Node.js環境でJestをゼロからセットアップし、最初のテストを書いて実行するまでの手順を解説します。テストファイルの命名規則、describe/it/expectの基本構文、Watchモードによる効率的なテスト開発、そしてカバレッジレポートの取得方法まで、TDDを始めるために必要な知識を網羅的に学べます。
Jestとは#
JestはJavaScript/TypeScript向けのテスティングフレームワークです。2025年現在、Jest 30.xが最新の安定版としてリリースされており、以下の特徴を持っています。
Jestの主な特徴#
| 特徴 |
説明 |
| ゼロコンフィグ |
多くのJavaScriptプロジェクトで設定なしに動作 |
| 高速な実行 |
並列テスト実行とインテリジェントなテスト選択 |
| スナップショットテスト |
UIコンポーネントの変更検出が容易 |
| 組み込みモック |
外部依存のモック機能を標準搭載 |
| コードカバレッジ |
追加設定なしでカバレッジレポートを生成 |
| Watchモード |
ファイル変更時の自動テスト再実行 |
Jestが選ばれる理由#
Jestは単なるテストランナーではなく、以下のツールを統合したオールインワンのソリューションです。
- テストランナー: テストの検出と実行
- アサーションライブラリ:
expectによる期待値検証
- モッキングライブラリ: 関数やモジュールのモック
- カバレッジツール: コードカバレッジの計測とレポート
これにより、複数のツールを組み合わせる手間なく、すぐにテストを書き始められます。
前提条件#
Jest環境を構築するには、以下が必要です。
- Node.js: v18.x以上(Jest 30はNode.js 18以上が必須)
- npmまたはyarn: パッケージマネージャー
- テキストエディタ: VS Code推奨
Node.jsがインストールされているか確認するには、ターミナルで以下を実行します。
プロジェクトのセットアップ#
まず、新しいNode.jsプロジェクトを作成します。
プロジェクトディレクトリの作成#
1
2
3
|
mkdir jest-tdd-tutorial
cd jest-tdd-tutorial
npm init -y
|
これにより、package.jsonが生成されます。
Jestのインストール#
Jestを開発依存としてインストールします。
1
|
npm install --save-dev jest
|
インストール後、package.jsonのdevDependenciesにJestが追加されていることを確認してください。
1
2
3
4
5
|
{
"devDependencies": {
"jest": "^30.0.0"
}
}
|
テストスクリプトの追加#
package.jsonのscriptsセクションにテスト実行コマンドを追加します。
1
2
3
4
5
6
7
|
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
}
|
| スクリプト |
説明 |
npm test |
すべてのテストを1回実行 |
npm run test:watch |
Watchモードでテストを実行(ファイル変更時に自動再実行) |
npm run test:coverage |
カバレッジレポート付きでテストを実行 |
最初のテストを書く#
環境が整ったら、最初のテストを書いてみましょう。TDDの原則に従い、まずテストを書き、次に実装を行います。
テストファイルの作成#
Jestはデフォルトで以下のパターンのファイルをテストとして認識します。
__tests__フォルダ内の.js、.jsx、.ts、.tsxファイル
.test.jsまたは.spec.jsで終わるファイル
.test.tsまたは.spec.tsで終わるファイル
プロジェクトルートにsum.test.jsを作成します。
1
2
3
4
5
6
|
// sum.test.js
const sum = require('./sum');
test('1 + 2 は 3 になる', () => {
expect(sum(1, 2)).toBe(3);
});
|
実装ファイルの作成#
テストに対応するsum.jsを作成します。
1
2
3
4
5
6
|
// sum.js
function sum(a, b) {
return a + b;
}
module.exports = sum;
|
テストの実行#
ターミナルで以下を実行します。
成功すると、以下のような出力が表示されます。
1
2
3
4
5
6
7
|
PASS ./sum.test.js
✓ 1 + 2 は 3 になる (2 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 0.5 s
|
describe/it/expectの基本構文#
Jestのテストは、3つの主要な構成要素で成り立っています。
describe - テストスイートのグループ化#
describeは関連するテストをグループ化するために使用します。
1
2
3
|
describe('Calculator', () => {
// 関連するテストをここにまとめる
});
|
describeはネストすることもできます。
1
2
3
4
5
6
7
8
9
|
describe('Calculator', () => {
describe('足し算', () => {
// 足し算に関するテスト
});
describe('引き算', () => {
// 引き算に関するテスト
});
});
|
test/it - 個別のテストケース#
testとitは同じ機能を持ち、個別のテストケースを定義します。
1
2
3
4
5
6
7
8
9
|
describe('Calculator', () => {
test('2つの数値を足す', () => {
expect(sum(1, 2)).toBe(3);
});
it('should add two numbers', () => {
expect(sum(3, 4)).toBe(7);
});
});
|
testとitの使い分けに厳密なルールはありませんが、一般的には以下の傾向があります。
| 関数 |
使用傾向 |
test |
日本語のテスト名でよく使用 |
it |
英語のBDD形式(“it should…")でよく使用 |
expect - アサーション(期待値の検証)#
expectは値を検証するための関数です。マッチャー(matcher)と組み合わせて使用します。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// 等値比較
expect(2 + 2).toBe(4);
// オブジェクトの深い比較
expect({ name: 'Alice' }).toEqual({ name: 'Alice' });
// 真偽値の検証
expect(true).toBeTruthy();
expect(false).toBeFalsy();
// null/undefinedの検証
expect(null).toBeNull();
expect(undefined).toBeUndefined();
|
主要なマッチャー#
Jestには豊富なマッチャーが用意されています。
等値比較#
1
2
3
4
5
6
7
8
9
|
// プリミティブ値の厳密な等値比較
expect(2 + 2).toBe(4);
// オブジェクトや配列の深い等値比較
expect({ a: 1, b: 2 }).toEqual({ a: 1, b: 2 });
expect([1, 2, 3]).toEqual([1, 2, 3]);
// より厳密な比較(undefined プロパティも考慮)
expect({ a: 1 }).toStrictEqual({ a: 1 });
|
数値比較#
1
2
3
4
5
6
7
|
expect(10).toBeGreaterThan(5);
expect(10).toBeGreaterThanOrEqual(10);
expect(5).toBeLessThan(10);
expect(5).toBeLessThanOrEqual(5);
// 浮動小数点数の比較(丸め誤差を考慮)
expect(0.1 + 0.2).toBeCloseTo(0.3);
|
文字列の検証#
1
2
3
|
expect('Hello World').toMatch(/World/);
expect('Hello World').toContain('World');
expect('Hello World').toHaveLength(11);
|
配列・オブジェクトの検証#
1
2
3
4
5
6
7
|
// 配列に要素が含まれているか
expect([1, 2, 3]).toContain(2);
expect([{ id: 1 }, { id: 2 }]).toContainEqual({ id: 1 });
// オブジェクトにプロパティがあるか
expect({ name: 'Alice', age: 30 }).toHaveProperty('name');
expect({ name: 'Alice', age: 30 }).toHaveProperty('age', 30);
|
例外の検証#
1
2
3
4
5
6
7
|
function throwError() {
throw new Error('Something went wrong');
}
expect(() => throwError()).toThrow();
expect(() => throwError()).toThrow('Something went wrong');
expect(() => throwError()).toThrow(Error);
|
否定の検証#
.notを使用して否定の検証ができます。
1
2
3
|
expect(5).not.toBe(3);
expect([1, 2, 3]).not.toContain(4);
expect({ name: 'Alice' }).not.toHaveProperty('age');
|
設定ファイルの作成#
Jestの動作をカスタマイズするには、設定ファイルを作成します。
jest.config.jsの作成#
プロジェクトルートにjest.config.jsを作成します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
// jest.config.js
/** @type {import('jest').Config} */
const config = {
// テスト環境
testEnvironment: 'node',
// テストファイルのパターン
testMatch: ['**/__tests__/**/*.js', '**/*.test.js', '**/*.spec.js'],
// カバレッジの収集対象
collectCoverageFrom: [
'**/*.js',
'!**/node_modules/**',
'!**/coverage/**',
'!jest.config.js'
],
// カバレッジレポートの出力先
coverageDirectory: 'coverage',
// 詳細な出力
verbose: true
};
module.exports = config;
|
主要な設定オプション#
| オプション |
説明 |
デフォルト値 |
testEnvironment |
テスト実行環境 |
'node' |
testMatch |
テストファイルのパターン |
['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'] |
collectCoverage |
カバレッジ収集の有効化 |
false |
coverageThreshold |
カバレッジの閾値設定 |
なし |
verbose |
詳細な出力の有効化 |
false |
setupFilesAfterEnv |
テスト実行前のセットアップファイル |
[] |
package.jsonでの設定#
jest.config.jsの代わりに、package.json内で設定することもできます。
1
2
3
4
5
6
|
{
"jest": {
"testEnvironment": "node",
"verbose": true
}
}
|
プロジェクト構成のベストプラクティス#
実際のプロジェクトでは、以下のようなディレクトリ構成が推奨されます。
1
2
3
4
5
6
7
8
9
10
11
|
project-root/
├── src/
│ ├── calculator.js
│ └── utils/
│ └── helper.js
├── __tests__/
│ ├── calculator.test.js
│ └── utils/
│ └── helper.test.js
├── jest.config.js
└── package.json
|
または、テストファイルを実装ファイルと同じディレクトリに配置する方法もあります。
1
2
3
4
5
6
7
8
9
|
project-root/
├── src/
│ ├── calculator.js
│ ├── calculator.test.js
│ └── utils/
│ ├── helper.js
│ └── helper.test.js
├── jest.config.js
└── package.json
|
Watchモードの活用#
TDDではテストを頻繁に実行するため、Watchモードが非常に便利です。
Watchモードの起動#
Watchモードのインタラクティブオプション#
Watchモードでは、以下のキー操作が可能です。
| キー |
機能 |
a |
すべてのテストを実行 |
f |
失敗したテストのみ実行 |
o |
変更されたファイルに関連するテストのみ実行 |
p |
ファイル名でフィルター |
t |
テスト名でフィルター |
q |
Watchモードを終了 |
Enter |
テストを再実行 |
TDDワークフローでのWatchモード活用#
Watchモードを使ったTDDの流れは以下のとおりです。
flowchart LR
A[Watchモード起動] --> B[失敗するテストを書く]
B --> C[自動でテスト実行<br/>Red]
C --> D[最小限の実装]
D --> E[自動でテスト実行<br/>Green]
E --> F[リファクタリング]
F --> G[自動でテスト実行<br/>Green維持]
G --> B
style C fill:#ffcccc,stroke:#cc0000,color:#000000
style E fill:#ccffcc,stroke:#00cc00,color:#000000
style G fill:#ccffcc,stroke:#00cc00,color:#000000ファイルを保存するたびに自動でテストが実行されるため、Red-Green-Refactorサイクルを高速に回すことができます。
コードカバレッジの取得#
コードカバレッジは、テストがどれだけのコードを網羅しているかを示す指標です。
カバレッジレポートの生成#
実行すると、ターミナルにカバレッジサマリーが表示され、coverageディレクトリにHTMLレポートが生成されます。
1
2
3
4
5
6
|
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
sum.js | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
|
カバレッジ指標の読み方#
| 指標 |
説明 |
| Statements |
実行された文の割合 |
| Branches |
実行された分岐(if/elseなど)の割合 |
| Functions |
呼び出された関数の割合 |
| Lines |
実行された行の割合 |
カバレッジ閾値の設定#
プロジェクトに品質基準を設けるには、カバレッジ閾値を設定します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// jest.config.js
const config = {
collectCoverage: true,
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
}
};
module.exports = config;
|
閾値を下回ると、テストは失敗として扱われます。
実践例: 電卓モジュールのTDD#
学んだ内容を活かして、電卓モジュールをTDDで開発してみましょう。
ステップ1: 失敗するテストを書く(Red)#
__tests__/calculator.test.jsを作成します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
// __tests__/calculator.test.js
const Calculator = require('../src/calculator');
describe('Calculator', () => {
let calc;
beforeEach(() => {
calc = new Calculator();
});
describe('add', () => {
test('2つの正の数を足す', () => {
expect(calc.add(2, 3)).toBe(5);
});
test('負の数を足す', () => {
expect(calc.add(-1, -2)).toBe(-3);
});
test('0を足す', () => {
expect(calc.add(5, 0)).toBe(5);
});
});
describe('subtract', () => {
test('引き算を行う', () => {
expect(calc.subtract(5, 3)).toBe(2);
});
});
describe('multiply', () => {
test('掛け算を行う', () => {
expect(calc.multiply(3, 4)).toBe(12);
});
});
describe('divide', () => {
test('割り算を行う', () => {
expect(calc.divide(10, 2)).toBe(5);
});
test('0で割ると例外をスローする', () => {
expect(() => calc.divide(10, 0)).toThrow('0で割ることはできません');
});
});
});
|
この時点でテストを実行すると、すべて失敗します(Red)。
ステップ2: テストを通す最小限の実装(Green)#
src/calculator.jsを作成します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
// src/calculator.js
class Calculator {
add(a, b) {
return a + b;
}
subtract(a, b) {
return a - b;
}
multiply(a, b) {
return a * b;
}
divide(a, b) {
if (b === 0) {
throw new Error('0で割ることはできません');
}
return a / b;
}
}
module.exports = Calculator;
|
テストを実行すると、すべて成功します(Green)。
ステップ3: リファクタリング#
現時点ではシンプルな実装なので、大きなリファクタリングは不要ですが、例えばバリデーションを追加することができます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
// src/calculator.js
class Calculator {
#validateNumbers(...numbers) {
for (const num of numbers) {
if (typeof num !== 'number' || Number.isNaN(num)) {
throw new TypeError('引数は有効な数値である必要があります');
}
}
}
add(a, b) {
this.#validateNumbers(a, b);
return a + b;
}
subtract(a, b) {
this.#validateNumbers(a, b);
return a - b;
}
multiply(a, b) {
this.#validateNumbers(a, b);
return a * b;
}
divide(a, b) {
this.#validateNumbers(a, b);
if (b === 0) {
throw new Error('0で割ることはできません');
}
return a / b;
}
}
module.exports = Calculator;
|
リファクタリング後も、すべてのテストがパスすることを確認します。
セットアップとティアダウン#
テストの前後で共通の処理を実行するには、以下のフック関数を使用します。
利用可能なフック関数#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
describe('フック関数のデモ', () => {
beforeAll(() => {
// すべてのテストの前に1回だけ実行
console.log('テストスイート開始');
});
afterAll(() => {
// すべてのテストの後に1回だけ実行
console.log('テストスイート終了');
});
beforeEach(() => {
// 各テストの前に実行
console.log('テスト開始');
});
afterEach(() => {
// 各テストの後に実行
console.log('テスト終了');
});
test('テスト1', () => {
expect(true).toBe(true);
});
test('テスト2', () => {
expect(false).toBe(false);
});
});
|
実行順序#
1
2
3
4
5
6
7
8
|
テストスイート開始
テスト開始
(テスト1実行)
テスト終了
テスト開始
(テスト2実行)
テスト終了
テストスイート終了
|
トラブルシューティング#
よくある問題と解決策#
| 問題 |
原因 |
解決策 |
Cannot find module |
モジュールパスの誤り |
相対パスを確認、moduleNameMapperを設定 |
| テストが検出されない |
ファイル名のパターン不一致 |
.test.jsまたは.spec.jsで終わるか確認 |
| ESモジュールのエラー |
CommonJSとESMの混在 |
Babelまたは--experimental-vm-modulesを使用 |
| 非同期テストのタイムアウト |
テストの完了を待機していない |
async/awaitまたはdoneコールバックを使用 |
ESモジュール対応#
ES Modulesを使用する場合は、以下の設定が必要です。
1
2
3
4
5
6
7
|
// jest.config.js
const config = {
transform: {},
extensionsToTreatAsEsm: ['.js']
};
export default config;
|
package.jsonに"type": "module"を追加し、テスト実行時に以下のフラグを使用します。
1
|
node --experimental-vm-modules node_modules/jest/bin/jest.js
|
まとめ#
本記事では、JavaScript(Node.js)環境でJestを使ったTDD環境構築について解説しました。
学んだ内容を振り返ると、以下のポイントが重要です。
- Jestのインストールと設定:
npm install --save-dev jestで簡単に導入可能
- テストファイルの命名規則:
.test.jsまたは.spec.js、あるいは__tests__ディレクトリに配置
- 基本構文:
describeでグループ化、test/itでテストケース定義、expectで検証
- 豊富なマッチャー:
toBe、toEqual、toThrowなど、様々な検証パターンに対応
- Watchモード: TDDの高速なフィードバックループを実現
- コードカバレッジ: テストの網羅性を定量的に把握
Jestの環境が整ったら、次のステップとしてTDDの実践に進みましょう。FizzBuzz問題などのシンプルな課題から始めて、Red-Green-Refactorサイクルを体験することをお勧めします。
参考リンク#