nock の使い方 — Node.jsのHTTPモックライブラリ完全ガイド
一言でいうと
nockは、Node.jsのhttp.requestをオーバーライドすることで、外部HTTPリクエストをインターセプトし、任意のレスポンスを返すテスト用モックライブラリです。実際のネットワーク通信を行わずに、HTTPクライアントの振る舞いを検証できます。
どんな時に使う?
- 外部APIに依存するモジュールのユニットテスト — GitHub API、AWS SDK、Stripe APIなど、外部サービスへのHTTPリクエストを含むコードを、ネットワーク接続なしでテストしたい場合
- エラーハンドリングの検証 — タイムアウト、500エラー、ネットワーク障害など、本番環境では再現しにくい異常系レスポンスをシミュレートしたい場合
- リクエスト内容のアサーション — 自分のコードが正しいURL・ヘッダー・ボディでHTTPリクエストを送信しているかを検証したい場合
インストール
# npm
npm install --save-dev nock
# yarn
yarn add --dev nock
# pnpm
pnpm add -D nock
注意: nock v14.x は現在メンテナンスされているNode.jsバージョンをサポートしています。
基本的な使い方
最もよく使うパターンは、特定のURLへのリクエストをインターセプトし、固定のレスポンスを返すケースです。
import nock from 'nock';
import axios from 'axios';
// テストのセットアップ
const scope = nock('https://api.github.com')
.get('/repos/atom/atom/license')
.reply(200, {
license: {
key: 'mit',
name: 'MIT License',
spdx_id: 'MIT',
},
});
// テスト対象のコードを実行
const response = await axios.get(
'https://api.github.com/repos/atom/atom/license'
);
console.log(response.data.license.key); // => 'mit'
// すべてのインターセプタが消費されたか確認
scope.isDone(); // => true
重要な仕組み: nockで定義したインターセプタは、一度使用されると自動的に削除されます。つまり、同じURLへのリクエストが2回あるなら、インターセプタも2つ定義する必要があります。
よく使うAPI
1. nock().reply() — レスポンスの定義
最も基本的なAPIです。ステータスコード、ボディ、ヘッダーを指定できます。
import nock from 'nock';
// 静的なレスポンス
nock('https://api.example.com')
.get('/users/1')
.reply(200, { id: 1, name: 'Alice' }, { 'X-Custom-Header': 'value' });
// 関数でレスポンスを動的に生成
nock('https://api.example.com')
.post('/users')
.reply(201, (uri: string, requestBody: Record<string, unknown>) => {
return { id: 999, ...requestBody };
});
2. nock().persist() — インターセプタの永続化
デフォルトでは1回使うと消えるインターセプタを、何度でも使えるようにします。
import nock from 'nock';
const scope = nock('https://api.example.com')
.get('/health')
.reply(200, { status: 'ok' })
.persist();
// 何度リクエストしても同じレスポンスが返る
await fetch('https://api.example.com/health'); // 200
await fetch('https://api.example.com/health'); // 200(消えない)
// テスト後にクリーンアップ
scope.persist(false);
nock.cleanAll();
3. リクエストボディ・クエリのマッチング
POSTボディやクエリパラメータを条件に含めることができます。
import nock from 'nock';
// リクエストボディのマッチング
nock('https://api.example.com')
.post('/login', { username: 'admin', password: 'secret' })
.reply(200, { token: 'abc123' });
// 正規表現でボディをマッチング
nock('https://api.example.com')
.post('/login', /admin/)
.reply(200, { token: 'abc123' });
// クエリパラメータのマッチング
nock('https://api.example.com')
.get('/users')
.query({ page: 1, limit: 10 })
.reply(200, [{ id: 1, name: 'Alice' }]);
// クエリパラメータを正規表現やbooleanで柔軟にマッチング
nock('https://api.example.com')
.get('/search')
.query(true) // 任意のクエリパラメータを許容
.reply(200, []);
4. エラーレスポンスとネットワークエラーのシミュレーション
異常系テストに不可欠な機能です。
import nock from 'nock';
// HTTPエラーステータス
nock('https://api.example.com')
.get('/not-found')
.reply(404, { error: 'Not Found' });
// ネットワークエラー(接続失敗)
nock('https://api.example.com')
.get('/timeout')
.replyWithError('ETIMEDOUT');
// 構造化されたエラーオブジェクト
nock('https://api.example.com')
.get('/connection-reset')
.replyWithError({
message: 'Connection reset',
code: 'ECONNRESET',
});
// レスポンス遅延のシミュレーション
nock('https://api.example.com')
.get('/slow')
.delay(5000) // 5秒遅延
.reply(200, { data: 'finally' });
// 接続遅延とボディ遅延を個別に指定
nock('https://api.example.com')
.get('/very-slow')
.delayConnection(2000) // 接続に2秒
.delayBody(3000) // ボディ送信に3秒
.reply(200, { data: 'done' });
5. nock.cleanAll() / nock.restore() — クリーンアップ
テスト間の干渉を防ぐために、適切なクリーンアップが重要です。
import nock from 'nock';
// すべてのインターセプタを削除
nock.cleanAll();
// nockによるhttp.requestのオーバーライドを完全に解除
nock.restore();
// 再度有効化する場合
if (!nock.isActive()) {
nock.activate();
}
// 実際のHTTPリクエストを禁止(モック漏れの検出に有用)
nock.disableNetConnect();
// localhostだけは許可(テストサーバー用)
nock.disableNetConnect();
nock.enableNetConnect('127.0.0.1');
// すべてのHTTPリクエストを再度許可
nock.enableNetConnect();
類似パッケージとの比較
| 特徴 | nock | msw | sinon (fake server) |
|---|---|---|---|
| 動作レイヤー | http.requestをオーバーライド | Service Workerまたはリクエストインターセプト | XMLHttpRequestをモック |
| ブラウザ対応 | ❌ Node.js専用 | ✅ ブラウザ + Node.js | ✅ ブラウザ中心 |
fetch 対応 | ✅(Node.jsのfetch) | ✅ | △ |
| セットアップの手軽さ | ◎ 非常にシンプル | ○ やや設定が必要 | ○ |
| リクエスト記録 | ✅ 内蔵 | ❌ | ❌ |
| 型安全性 | △ | ○ | △ |
| 未マッチリクエストの検出 | ✅ | ✅ | △ |
| 主な用途 | Node.jsのユニットテスト | フルスタックのインテグレーションテスト | ブラウザ中心のテスト |
選定の目安:
- Node.js専用のユニットテストなら nock が最もシンプルで実績豊富
- ブラウザとNode.jsの両方で統一的にモックしたいなら msw
- 既にsinonを使っているプロジェクトなら sinon のfake serverも選択肢
注意点・Tips
インターセプタは使い捨て
最も多いハマりポイントです。nockのインターセプタはデフォルトで1回使うと消えます。
// ❌ 2回目のリクエストでエラーになる
nock('https://api.example.com').get('/data').reply(200, { ok: true });
await fetch('https://api.example.com/data'); // ✅ 成功
await fetch('https://api.example.com/data'); // ❌ エラー!
// ✅ 複数回呼ばれる場合は .persist() か .times() を使う
nock('https://api.example.com')
.get('/data')
.times(3) // 3回まで
.reply(200, { ok: true });
テストの後始末を忘れない
import nock from 'nock';
afterEach(() => {
nock.cleanAll(); // 未使用のインターセプタを削除
});
afterAll(() => {
nock.restore(); // http.requestを元に戻す
});
nock.disableNetConnect() でモック漏れを検出
テスト中に意図しない実HTTPリクエストが発生するのを防げます。
beforeAll(() => {
nock.disableNetConnect();
// ローカルのテストサーバーだけ許可する場合
nock.enableNetConnect(/127\.0\.0\.1|localhost/);
});
afterAll(() => {
nock.enableNetConnect();
});
scope.isDone() でインターセプタの消費を検証
定義したモックがすべて呼ばれたかを確認できます。テストの信頼性向上に有効です。
const scope = nock('https://api.example.com')
.get('/users')
.reply(200, []);
// テスト対象のコードを実行
await myFunction();
// すべてのインターセプタが使われたか検証
expect(scope.isDone()).toBe(true);
// 未消費のインターセプタを確認
console.log(scope.pendingMocks()); // => [] なら全部消費済み
ESModulesでの注意
ESModulesから直接httpをインポートしているコードでは、nockのインターセプトが効かない場合があります。これはESModulesのライブバインディングの仕組みに起因します。該当する場合は、HTTPクライアントライブラリ(axios, node-fetchなど)経由でリクエストするか、mswへの移行を検討してください。
Jestとのメモリ問題
Jestと組み合わせる場合、--maxWorkers を制限するか、テストファイルごとにnock.cleanAll()を確実に呼ぶことで、メモリリークを防げます。
まとめ
nockは、Node.jsにおけるHTTPモックのデファクトスタンダードとして長年使われてきた信頼性の高いライブラリです。nock(url).get(path).reply(status, body) という直感的なAPIで、外部API依存のコードを簡単にテストできます。インターセプタの使い捨て仕様とcleanAll()によるクリーンアップさえ押さえておけば、堅牢なテストスイートを構築できるでしょう。