Node.js 標準テストランナー完全理解:`node:test` がもたらす新しい DX
Node.js でテストを書く際、これまで Jest や Mocha といったサードパーティのテストフレームワークを導入することが当たり前でした。しかし、Node.js 18 から正式に安定版となった標準テストランナーnode:testにより、外部依存なしでテストを実行できる時代が到来しています。追加のパッケージインストールなしで、すぐにテスト駆動開発を始められる――この手軽さが、モダンな Node.js 開発に新しい DX(Developer Experience)をもたらしているのです。
本記事では、node:testの基本から実践的な活用方法まで、段階的に解説していきますね。
背景
Node.js におけるテスト環境の変遷
Node.js のエコシステムでは、長らく Mocha、Jest、AVA などのサードパーティ製テストフレームワークが主流でした。これらは強力な機能を持つ一方で、以下のような課題も抱えていました。
| # | 課題 | 詳細 |
|---|---|---|
| 1 | 依存関係の増加 | テストフレームワーク本体に加え、アサーションライブラリやモックライブラリなど複数のパッケージが必要 |
| 2 | 設定の複雑さ | babel 設定や TypeScript 設定、カバレッジツールの設定など、初期セットアップに時間がかかる |
| 3 | バージョン管理 | Node.js のバージョンアップ時に、テストフレームワーク側の互換性を確認する必要がある |
| 4 | パフォーマンス | 多機能ゆえに起動時間が長く、小規模プロジェクトでは過剰になることも |
こうした背景から、Node.js コアチームは標準機能としてのテストランナーを開発することを決定しました。
node:testの登場と安定化プロセス
node:testは以下のバージョンで段階的に導入されました。
| # | バージョン | ステータス | リリース時期 |
|---|---|---|---|
| 1 | Node.js 18.0.0 | Experimental(実験的機能) | 2022 年 4 月 |
| 2 | Node.js 18.13.0 | Stable(安定版) | 2023 年 1 月 |
| 3 | Node.js 20.0.0 | 機能拡張版 | 2023 年 4 月 |
Node.js 18.13.0 以降では、追加フラグなしで本番環境でも安心して利用できるようになりました。
以下の図は、Node.js のテスト環境がどのように変化してきたかを示しています。
mermaidflowchart TB
subgraph old["従来のアプローチ"]
app1["Node.js アプリ"]
jest["Jest / Mocha"]
assert["アサーション<br/>ライブラリ"]
mock["モック<br/>ライブラリ"]
app1 --> jest
jest --> assert
jest --> mock
end
subgraph new["node:test のアプローチ"]
app2["Node.js アプリ"]
nodetest["node:test<br/>(標準搭載)"]
builtin["組み込み<br/>アサーション"]
app2 --> nodetest
nodetest --> builtin
end
old -.->|シンプル化| new
従来は複数の外部パッケージに依存していましたが、node:testにより Node.js 本体だけでテストが完結するようになりました。
課題
サードパーティフレームワークの依存問題
多くの開発者が直面していた課題は、テストを書くだけのために大量の依存関係を追加しなければならないという点でした。
依存関係の肥大化
典型的な Jest ベースのプロジェクトでは、以下のようなパッケージが必要です。
javascript// package.json の例
{
"devDependencies": {
"jest": "^29.7.0",
"@types/jest": "^29.5.0",
"ts-jest": "^29.1.0",
"@jest/globals": "^29.7.0",
"jest-mock-extended": "^3.0.5"
}
}
上記の依存関係だけで、node_modulesのサイズは数百 MB に達することも珍しくありません。
バージョン互換性の維持コスト
Node.js をアップグレードする際、テストフレームワーク側の対応を待つ必要があり、最新機能の恩恵を即座に受けられないケースがありました。
以下の表は、各フレームワークの対応状況を示しています。
| # | フレームワーク | Node.js 新バージョン対応 | 設定ファイル数 | 学習コスト |
|---|---|---|---|---|
| 1 | Jest | リリースから 1〜2 ヶ月後 | 2〜3 個 | ★★★ |
| 2 | Mocha | リリースから 1〜2 週間後 | 1〜2 個 | ★★ |
| 3 | AVA | リリースから 1 ヶ月後 | 1 個 | ★★ |
| 4 | node:test | 即座(標準機能) | 不要 | ★ |
初心者の参入障壁
Node.js を学び始めた初心者にとって、「テストを書くためにまず設定を理解しなければならない」という状況は大きな障壁でした。
javascript// Jest の設定例 - 初心者には難解
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
collectCoverageFrom: [
'src/**/*.{js,ts}',
'!src/**/*.d.ts',
],
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
};
上記のような設定ファイルを理解する前に、まずテストそのものの書き方を学びたいというニーズがありました。
以下の図は、従来のテスト環境構築に必要な学習ステップを示しています。
mermaidflowchart LR
start["テストを<br/>書きたい"] --> step1["フレームワーク<br/>選定"]
step1 --> step2["インストール"]
step2 --> step3["設定ファイル<br/>作成"]
step3 --> step4["型定義<br/>インストール"]
step4 --> step5["初めて<br/>テストを実行"]
style start fill:#e1f5ff
style step5 fill:#c8e6c9
テストを実行するまでに多くのステップを踏む必要があり、初心者には負担が大きかったのです。
解決策
node:testによる標準化
node:testは、上記の課題を根本的に解決する標準テストランナーとして設計されました。
ゼロ設定でテストが実行可能
node:testの最大の特徴は、追加のパッケージインストールや設定ファイルが一切不要という点です。
javascript// test.js - 最小限のテストコード
import { test } from 'node:test';
import assert from 'node:assert';
test('足し算のテスト', () => {
assert.strictEqual(1 + 1, 2);
});
上記のコードを保存して、以下のコマンドを実行するだけです。
bashnode --test test.js
設定ファイルは不要で、すぐにテストが実行されます。
組み込みアサーション
Node.js の標準モジュールnode:assertを使用することで、別途アサーションライブラリを追加する必要がありません。
javascriptimport assert from 'node:assert';
// 厳密な等価性チェック
assert.strictEqual(actual, expected);
// オブジェクトの深い比較
assert.deepStrictEqual(actualObj, expectedObj);
// 例外のテスト
assert.throws(() => {
throw new Error('エラー');
}, /エラー/);
これにより、依存関係を最小限に保ちながら、十分な検証機能が利用できますね。
並列実行と高速化
node:testは、デフォルトでテストを並列実行する設計になっており、大規模なテストスイートでも高速に実行できます。
bash# 自動的に並列実行される
node --test tests/
# 並列数を指定することも可能
node --test --test-concurrency=4 tests/
以下の図は、node:testの実行フローを示しています。
mermaidflowchart TB
start["テスト実行<br/>コマンド"] --> discover["テストファイル<br/>検出"]
discover --> parallel["並列実行<br/>スケジューラー"]
parallel --> test1["テスト1"]
parallel --> test2["テスト2"]
parallel --> test3["テスト3"]
test1 --> collect["結果収集"]
test2 --> collect
test3 --> collect
collect --> report["レポート<br/>出力"]
並列実行により、従来のテストフレームワークと比較して実行時間を大幅に短縮できます。
ネイティブ TypeScript サポート
Node.js 18 以降では、--loaderフラグを使用することで、TypeScript ファイルを直接テストできます。
typescript// test.ts - TypeScript でテストを記述
import { test } from 'node:test';
import assert from 'node:assert';
test('型安全な足し算', () => {
const add = (a: number, b: number): number => a + b;
assert.strictEqual(add(1, 2), 3);
});
実行方法も簡単です。
bash# ts-node やトランスパイラ不要
node --loader tsx --test test.ts
外部トランスパイラを設定する必要がなく、TypeScript での開発体験も向上しました。
モックとスパイ機能
Node.js 20 以降では、モック機能も標準で提供されています。
javascriptimport { test, mock } from 'node:test';
import assert from 'node:assert';
test('関数のモック', () => {
// モック関数の作成
const mockFn = mock.fn();
mockFn('テスト');
// 呼び出し回数の確認
assert.strictEqual(mockFn.mock.calls.length, 1);
// 引数の確認
assert.strictEqual(
mockFn.mock.calls[0].arguments[0],
'テスト'
);
});
上記のように、Jest スタイルのモック機能が標準で利用できるようになりました。
具体例
基本的なテストの書き方
ここからは、実際のプロジェクトでnode:testを活用する具体例を見ていきましょう。
シンプルな関数のテスト
まず、テスト対象となる関数を定義します。
javascript// src/math.js - テスト対象の関数
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
export function divide(a, b) {
if (b === 0) {
throw new Error('0で割ることはできません');
}
return a / b;
}
次に、これらの関数をテストするコードを作成します。
javascript// tests/math.test.js - テストコード
import { test, describe } from 'node:test';
import assert from 'node:assert';
import { add, multiply, divide } from '../src/math.js';
describeを使ってテストをグループ化します。
javascriptdescribe('算術関数のテスト', () => {
test('add 関数:正の数の足し算', () => {
assert.strictEqual(add(2, 3), 5);
});
test('add 関数:負の数の足し算', () => {
assert.strictEqual(add(-1, -1), -2);
});
});
次に、multiply 関数のテストを追加します。
javascriptdescribe('算術関数のテスト', () => {
// ... 前述のテスト
test('multiply 関数:正の数の掛け算', () => {
assert.strictEqual(multiply(3, 4), 12);
});
test('multiply 関数:0との掛け算', () => {
assert.strictEqual(multiply(5, 0), 0);
});
});
例外処理のテストも記述できます。
javascriptdescribe('算術関数のテスト', () => {
// ... 前述のテスト
test('divide 関数:正常な除算', () => {
assert.strictEqual(divide(10, 2), 5);
});
test('divide 関数:0除算でエラー', () => {
assert.throws(
() => divide(10, 0),
/0で割ることはできません/
);
});
});
テストを実行します。
bashnode --test tests/math.test.js
非同期処理のテスト
非同期関数のテスト対象を定義します。
javascript// src/api.js - 非同期処理の例
export async function fetchUser(userId) {
// 模擬的なAPI呼び出し
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id: userId, name: 'テストユーザー' });
}, 100);
});
}
非同期関数のテストは、async/awaitを使用して記述します。
javascript// tests/api.test.js
import { test } from 'node:test';
import assert from 'node:assert';
import { fetchUser } from '../src/api.js';
test('fetchUser:ユーザー情報の取得', async () => {
const user = await fetchUser(1);
assert.strictEqual(user.id, 1);
assert.strictEqual(user.name, 'テストユーザー');
});
複数の非同期処理をテストする場合も同様です。
javascripttest('fetchUser:複数ユーザーの同時取得', async () => {
const [user1, user2] = await Promise.all([
fetchUser(1),
fetchUser(2),
]);
assert.strictEqual(user1.id, 1);
assert.strictEqual(user2.id, 2);
});
以下の図は、非同期テストの実行フローを示しています。
mermaidsequenceDiagram
participant test as テストランナー
participant func as fetchUser
participant timer as setTimeout
test->>func: fetchUser(1) 呼び出し
func->>timer: 100ms 待機開始
Note over timer: 非同期処理中
timer-->>func: 完了
func-->>test: ユーザー情報返却
test->>test: アサーション実行
node:testは、Promise ベースの非同期処理を自然に扱うことができます。
モックを使った外部依存のテスト
実際のアプリケーションでは、外部 API やデータベースへの依存を持つコードをテストする必要があります。
外部 API 呼び出しのモック
外部 API を呼び出すモジュールを定義します。
javascript// src/userService.js
import https from 'https';
export async function getUserFromAPI(userId) {
return new Promise((resolve, reject) => {
https
.get(
`https://api.example.com/users/${userId}`,
(res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
resolve(JSON.parse(data));
});
}
)
.on('error', reject);
});
}
このモジュールをテストする際、実際の API を呼び出すのは望ましくありません。モックを使用します。
javascript// tests/userService.test.js
import { test, mock } from 'node:test';
import assert from 'node:assert';
import * as userService from '../src/userService.js';
test('getUserFromAPI:モックを使用したテスト', async () => {
// getUserFromAPI 関数をモック
const mockGetUser = mock.method(
userService,
'getUserFromAPI',
async (userId) => {
return { id: userId, name: 'モックユーザー' };
}
);
const user = await userService.getUserFromAPI(1);
assert.strictEqual(user.name, 'モックユーザー');
assert.strictEqual(mockGetUser.mock.calls.length, 1);
});
上記のコードでは、実際の HTTP リクエストは発生せず、モック関数が代わりに実行されます。
タイマーのモック
setTimeoutやsetIntervalを使うコードも、モックを使えば高速にテストできます。
javascript// src/scheduler.js
export function delayedGreeting(name, delay) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(`こんにちは、${name}さん`);
}, delay);
});
}
Node.js 20 以降では、タイマーのモック機能が標準搭載されています。
javascript// tests/scheduler.test.js
import { test, mock } from 'node:test';
import assert from 'node:assert';
import { delayedGreeting } from '../src/scheduler.js';
test('delayedGreeting:タイマーのモック', async () => {
// タイマーをモック化
mock.timers.enable({ apis: ['setTimeout'] });
const promise = delayedGreeting('太郎', 5000);
// 時間を進める
mock.timers.tick(5000);
const result = await promise;
assert.strictEqual(result, 'こんにちは、太郎さん');
mock.timers.reset();
});
上記のテストは、実際には 5 秒待機せずに即座に完了します。
テストフック(setup/teardown)
テストの前後で共通の処理を実行したい場合、フック機能を使用できます。
javascript// tests/database.test.js
import {
test,
describe,
before,
after,
beforeEach,
afterEach,
} from 'node:test';
import assert from 'node:assert';
describe('データベーステスト', () => {
let db;
// 全テスト実行前に1回だけ実行
before(async () => {
db = await connectDatabase();
console.log('データベース接続完了');
});
// 各テスト実行前に毎回実行
beforeEach(async () => {
await db.query('BEGIN TRANSACTION');
});
// 各テスト実行後に毎回実行
afterEach(async () => {
await db.query('ROLLBACK');
});
// 全テスト実行後に1回だけ実行
after(async () => {
await db.close();
console.log('データベース接続解除');
});
test('ユーザーの作成', async () => {
const user = await db.createUser({ name: '太郎' });
assert.strictEqual(user.name, '太郎');
});
test('ユーザーの取得', async () => {
await db.createUser({ name: '花子' });
const user = await db.findUser({ name: '花子' });
assert.strictEqual(user.name, '花子');
});
});
上記のようにフックを活用することで、各テストを独立した状態で実行できます。
以下の図は、フックの実行順序を示しています。
mermaidflowchart TB
start["テストスイート<br/>開始"] --> before["before<br/>(初期化)"]
before --> test1start["テスト1 開始"]
test1start --> beforeEach1["beforeEach"]
beforeEach1 --> test1["テスト1 実行"]
test1 --> afterEach1["afterEach"]
afterEach1 --> test2start["テスト2 開始"]
test2start --> beforeEach2["beforeEach"]
beforeEach2 --> test2["テスト2 実行"]
test2 --> afterEach2["afterEach"]
afterEach2 --> after["after<br/>(クリーンアップ)"]
after --> done["テストスイート<br/>終了"]
style before fill:#e3f2fd
style after fill:#e3f2fd
style beforeEach1 fill:#fff3e0
style afterEach1 fill:#fff3e0
style beforeEach2 fill:#fff3e0
style afterEach2 fill:#fff3e0
この仕組みにより、テストごとに環境をリセットでき、テスト間の干渉を防げます。
カバレッジレポートの取得
Node.js 20 以降では、コードカバレッジ機能も標準搭載されています。
bash# カバレッジを有効にしてテスト実行
node --test --experimental-test-coverage tests/
実行結果の例は以下のようになります。
bash# 出力例
✔ tests/math.test.js (4 tests) 12ms
✔ tests/api.test.js (2 tests) 115ms
Coverage report:
File | Line % | Branch % | Func % | Uncov. Lines
-------------|--------|----------|--------|-------------
src/math.js | 100.00 | 100.00 | 100.00 |
src/api.js | 85.71 | 50.00 | 66.67 | 15-17
-------------|--------|----------|--------|-------------
All files | 92.86 | 75.00 | 83.33 |
上記のレポートにより、どの部分がテストされていないかが一目でわかります。
CI/CD 環境での活用
GitHub Actions などの CI 環境でも、node:testはシンプルに統合できます。
yaml# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x, 20.x, 22.x]
steps:
- uses: actions/checkout@v4
- name: Node.js のセットアップ
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: 依存関係のインストール
run: yarn install
- name: テスト実行
run: node --test tests/
- name: カバレッジレポート生成
run: node --test --experimental-test-coverage tests/
追加のテストライブラリをインストールする必要がないため、CI の実行時間も短縮されます。
以下の図は、CI/CD パイプラインにおけるnode:testの位置づけを示しています。
mermaidflowchart LR
push["コード<br/>プッシュ"] --> ci["CI 起動"]
ci --> setup["Node.js<br/>セットアップ"]
setup --> install["依存関係<br/>インストール"]
install --> test["node --test<br/>実行"]
test --> coverage["カバレッジ<br/>計測"]
coverage --> result{"テスト<br/>結果"}
result -->|成功| deploy["デプロイ"]
result -->|失敗| notify["通知"]
style test fill:#c8e6c9
style deploy fill:#bbdefb
style notify fill:#ffccbc
node:testを使うことで、CI 環境でのセットアップが簡素化され、テストの実行速度も向上しますね。
まとめ
Node.js 標準テストランナーnode:testは、以下のような革新的な価値を開発者にもたらしています。
技術的メリット
- ゼロ依存: 外部パッケージのインストールが不要で、
package.jsonのdevDependenciesをスリム化できる - 高速実行: 並列実行がデフォルトで有効化されており、大規模テストスイートでもパフォーマンスが良好
- 即座の互換性: Node.js のアップデートと同時に最新機能が利用可能
開発体験の向上
- 学習コストの低減: 設定不要で即座にテストを書き始められるため、初心者でも取り組みやすい
- 統一されたエコシステム: Node.js コアチームによる公式サポートで、長期的な安定性が保証される
- モダンな機能: モック、スパイ、カバレッジなど、必要な機能が標準で揃っている
プロジェクト運用面
- メンテナンスコストの削減: テストフレームワークのバージョン管理や互換性確認の手間が不要
- CI/CD の簡素化: セットアップステップが少なく、実行時間も短縮される
- チーム内の統一: 標準機能のため、プロジェクト間で設定を統一しやすい
Node.js 18 以降を採用している、あるいは採用予定のプロジェクトでは、node:testへの移行を検討する価値が十分にあります。既存の Jest や Mocha からの移行も、API の類似性により比較的スムーズに進められるでしょう。
これから Node.js でテストを書く方にとって、node:testは「まず試してみるべき第一選択肢」となることは間違いありません。シンプルで強力、そして標準という安心感――この新しい DX を、ぜひ体験してみてください。
関連リンク
articleNode.js 標準テストランナー完全理解:`node:test` がもたらす新しい DX
articleNode.js で ESM の `ERR_MODULE_NOT_FOUND` を解く:解決策総当たりチェックリスト
articleNode.js 本番メモリ運用:ヒープ/外部メモリ/リーク検知の継続監視
articleNode.js で社内 RPA:Playwright でブラウザ自動化&失敗回復の流儀
articleNode.js × Fastify で爆速 REST API:スキーマ駆動とプラグイン設計を学ぶ
articleNode.js クリーンアーキテクチャ実践:アダプタ/ユースケース/エンティティの分離
articleNotebookLM 活用事例:営業提案書の下書きと顧客要件の整理を自動化
articleGrok RAG 設計入門:社内ドキュメント検索を高精度にする構成パターン
articlegpt-oss 運用監視ダッシュボード設計:Prometheus/Grafana/OTel で可観測性強化
articleNode.js 標準テストランナー完全理解:`node:test` がもたらす新しい DX
articleNext.js の Route Handlers で multipart/form-data が受け取れない問題の切り分け術
articleMCP サーバー で社内ナレッジ検索チャットを構築:権限制御・要約・根拠表示の実装パターン
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来