本比較では、フロントエンド開発およびテストにおいて HTTP リクエストをインターセプトし、モックレスポンスを返すための主要なライブラリ群を扱います。msw や miragejs はアプリケーションレベルでのモック化を、axios-mock-adapter や nock は特定の HTTP クライアントやノード環境でのインターセプトを、json-server は設定ファイルベースの簡易サーバーを、faker は偽データ生成をそれぞれ担当します。開発フェーズ(プロトタイピング、結合テスト、ユニットテスト)や実行環境(ブラウザ、Node.js)に応じて適切なツールを選定するための指針を提供します。
現代のフロントエンド開発において、バックエンド API の完成を待たずに開発を進めることや、テスト環境で安定したレスポンスを確保することは不可欠です。msw、miragejs、axios-mock-adapter、nock、json-server、faker は、それぞれ異なるアプローチでこの課題を解決しようとします。本稿では、これらのツールがどのように動作し、どの状況で選ぶべきかを技術的な観点から深掘りします。
モックツールの選定で最も重要なのは、どこでリクエストを捉えるかという点です。これにより、テストの信頼性とセットアップの手間が決まります。
msw は Service Worker を利用し、ブラウザのネットワークレベルでリクエストをインターセプトします。
fetch や axios)を変更せずにモックできます。// msw: Service Worker によるネットワークインターセプト
import { http, HttpResponse } from 'msw';
import { setupWorker } from 'msw/browser';
const worker = setupWorker(
http.get('/api/users', () => {
return HttpResponse.json({ id: 1, name: 'Alice' });
})
);
worker.start();
miragejs は、アプリケーション内に仮想サーバーを構築し、XMLHttpRequest や Fetch をフックします。
// miragejs: アプリケーション内仮想サーバー
import { createServer } from 'miragejs';
createServer({
routes() {
this.get('/api/users', () => {
return { id: 1, name: 'Alice' };
});
}
});
axios-mock-adapter は、Axios ライブラリ内部のインターセプターを利用します。
// axios-mock-adapter: Axios インターセプター
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
const mock = new MockAdapter(axios);
mock.onGet('/api/users').reply(200, { id: 1, name: 'Alice' });
nock は、Node.js 環境で HTTP ソケットレベルをインターセプトします。
// nock: Node.js HTTP ソケットインターセプト
import nock from 'nock';
nock('http://api.example.com')
.get('/users')
.reply(200, { id: 1, name: 'Alice' });
json-server は、実際の Node.js サーバーを起動し、ファイルベースでレスポンスを返します。
# json-server: CLI によるサーバー起動
echo '{ "users": [ { "id": 1, "name": "Alice" } ] }' > db.json
npx json-server --watch db.json
faker はリクエストインターセプト機能を持ちません。
// faker: 偽データ生成(※非推奨パッケージ)
import faker from 'faker';
// 注意:このパッケージはメンテナンス終了済みです
const name = faker.name.findName();
ツールの動作環境は、テスト戦略に直結する重要な要素です。
msw: ブラウザと Node.js の両方に対応。ユニバーサルなモック戦略が可能です。miragejs: 主にブラウザ環境を想定。Node での使用は限定的です。axios-mock-adapter: 環境を問いませんが、Axios 依存です。nock: Node.js 専用。ブラウザテストには使用できません。json-server: Node.js 上で動作する独立サーバー。faker: 環境を問いませんが、パッケージ自体が非推奨です。開発者が特に注意すべきは nock です。フロントエンドのユニットテストをブラウザ環境(Vitest, Jest jsdom)で実行する場合、nock は機能しません。一方、msw は環境を選ばないため、テストインフラの統一に適しています。
モックデータが静的か動的かも、ツールの選定理由になります。
json-server は静的な JSON ファイルに依存します。
// json-server: db.json
{
"posts": [
{ "id": 1, "title": "Hello" }
]
}
miragejs と msw は、コード内で動的にデータを生成できます。
faker(または @faker-js/faker)と組み合わせることで、毎回異なるデータを返せます。// msw + @faker-js/faker: 動的データ生成
import { faker } from '@faker-js/faker';
http.get('/api/users', () => {
return HttpResponse.json({
name: faker.person.fullName()
});
});
faker 単体ではデータ生成のみを行います。
faker パッケージは非推奨であり、@faker-js/faker への移行が必須です。// @faker-js/faker: 推奨される代替パッケージ
import { faker } from '@faker-js/faker';
const user = {
id: faker.string.uuid(),
email: faker.internet.email()
};
アーキテクチャ選定において、パッケージの将来性は無視できません。
faker: 公式に非推奨(Deprecated)となっています。セキュリティ修正や新機能は提供されないため、新規プロジェクトでの使用は避けてください。代わりに @faker-js/faker を使用します。miragejs: 機能は強力ですが、近年メンテナンスの頻度が低下しています。長期プロジェクトでは、より活発に開発されている msw への移行を検討する価値があります。msw: 現在最もアクティブにメンテナンスされており、コミュニティの支持も厚いです。axios-mock-adapter, nock, json-server: 特定のユースケースにおいて依然として有効ですが、それぞれ前述の環境制限や用途の限定性があります。| 用途 | 推奨パッケージ | 理由 |
|---|---|---|
| 現代のフロントエンド開発 | msw | ブラウザ/Node 両対応、ネットワークレベルインターセプト |
| Axios 限定のユニットテスト | axios-mock-adapter | 設定が簡単で Axios に特化 |
| Node.js 統合テスト | nock | HTTP ソケットレベルの確実なインターセプト |
| プロトタイピング | json-server | コードレスで即時 API 構築 |
| データ生成 | @faker-js/faker | faker は非推奨のためフォーク版を使用 |
| アプリ内モックサーバー | miragejs | ORM 機能があるがメンテナンスに注意 |
技術選定の結論として、新規プロジェクトでは msw を第一候補とすることを推奨します。ネットワークレベルでのインターセプトは、アプリケーションコードを汚さず、かつ本番環境に近い挙動を再現できるためです。データ生成には faker ではなく @faker-js/faker を使用し、パッケージの健全性を保つ必要があります。
nock は Node.js 固有のテストに、json-server は初期プロトタイピングに役割を任せ、コアとなる開発・テスト環境は msw で統一するのが — 現代的なフロントエンドアーキテクチャにおける — 最も堅実な選択です。
Axios を既に採用しているプロジェクトで、HTTP リクエストレベルではなく Axios インターセプターレベルでモックを扱いたい場合に選択します。設定が簡単で依存関係も少ないため、ユニットテストで特定の Axios 呼び出しをピンポイントで検証するのに最適です。ただし、Fetch API や他の HTTP クライアントには対応していないため、技術スタックが Axios に固定されている場合に限定されます。
このパッケージは現在メンテナンスが終了しており、非推奨です。偽データ生成が必要な場合は、コミュニティフォークである @faker-js/faker を使用すべきです。レガシープロジェクトの維持以外で新規採用することは避け、セキュリティリスクやバグ修正の不足に注意してください。
バックエンド実装前にフロントエンドのみでプロトタイピングを迅速に行いたい場合に選択します。コードを書かず JSON ファイル一つで REST API を立ち上げられるため、初期開発やデモ作成に極めて効率的です。ただし、複雑なロジックや認証処理には対応できないため、本番環境のモックとしては不向きです。
アプリケーション内部にモックサーバーを構築し、ブラウザ上で動作する統合テストや開発環境として利用したい場合に選択します。ORM 機能を持ち、データの関係性を定義できるため、複雑なデータ構造のモックに適しています。ただし、近年メンテナンス頻度が低下しているため、長期プロジェクトでは代替手段の検討が必要です。
ブラウザと Node.js 両方の環境で動作する、現代적인フロントエンド開発のデファクトスタンダードです。Service Worker を利用してネットワークレベルでリクエストをインターセプトするため、アプリケーションコードの変更 없이 モックを切り替えられます。アクティブにメンテナンスされており、型安全性や機能面でも最もバランスが取れています。
Node.js 環境でのバックエンド統合テストや、サーバーサイドの HTTP リクエストモック化に特化して選択します。ソケットレベルで HTTP リクエストをインターセプトするため、精度の高いテストが可能ですが、ブラウザ環境では動作しません。フロントエンド単体のテストというよりは、Node.js を介した E2E テストやバックエンドモックに適しています。
Axios adapter that allows to easily mock requests
Using npm:
$ npm install axios-mock-adapter --save-dev
It's also available as a UMD build:
axios-mock-adapter works on Node as well as in a browser, it works with axios v0.17.0 and above.
Mocking a GET request
const axios = require("axios");
const AxiosMockAdapter = require("axios-mock-adapter");
// This sets the mock adapter on the default instance
const mock = new AxiosMockAdapter(axios);
// Mock any GET request to /users
// arguments for reply are (status, data, headers)
mock.onGet("/users").reply(200, {
users: [{ id: 1, name: "John Smith" }],
});
axios.get("/users").then(function (response) {
console.log(response.data);
});
Mocking a GET request with specific parameters
const axios = require("axios");
const AxiosMockAdapter = require("axios-mock-adapter");
// This sets the mock adapter on the default instance
const mock = new AxiosMockAdapter(axios);
// Mock GET request to /users when param `searchText` is 'John'
// arguments for reply are (status, data, headers)
mock.onGet("/users", { params: { searchText: "John" } }).reply(200, {
users: [{ id: 1, name: "John Smith" }],
});
axios
.get("/users", { params: { searchText: "John" } })
.then(function (response) {
console.log(response.data);
});
When using params, you must match all key/value pairs passed to that option.
To add a delay to responses, specify a delay amount (in milliseconds) when instantiating the adapter
// All requests using this instance will have a 2 seconds delay:
const mock = new AxiosMockAdapter(axiosInstance, { delayResponse: 2000 });
You can restore the original adapter (which will remove the mocking behavior)
mock.restore();
You can also reset the registered mock handlers with resetHandlers
mock.resetHandlers();
You can reset both registered mock handlers and history items with reset
mock.reset();
reset is different from restore in that restore removes the mocking from the axios instance completely,
whereas reset only removes all mock handlers that were added with onGet, onPost, etc. but leaves the mocking in place.
Mock a low level network error
// Returns a failed promise with Error('Network Error');
mock.onGet("/users").networkError();
// networkErrorOnce can be used to mock a network error only once
mock.onGet("/users").networkErrorOnce();
Mock a network timeout
// Returns a failed promise with Error with code set to 'ECONNABORTED'
mock.onGet("/users").timeout();
// timeoutOnce can be used to mock a timeout only once
mock.onGet("/users").timeoutOnce();
Passing a function to reply
mock.onGet("/users").reply(function (config) {
// `config` is the axios config and contains things like the url
// return an array in the form of [status, data, headers]
return [
200,
{
users: [{ id: 1, name: "John Smith" }],
},
];
});
Passing a function to reply that returns an axios request, essentially mocking a redirect
mock.onPost("/foo").reply(function (config) {
return axios.get("/bar");
});
Using a regex
mock.onGet(/\/users\/\d+/).reply(function (config) {
// the actual id can be grabbed from config.url
return [200, {}];
});
Using variables in regex
const usersUri = "/users";
const url = new RegExp(`${usersUri}/*`);
mock.onGet(url).reply(200, users);
Specify no path to match by verb alone
// Reject all POST requests with HTTP 500
mock.onPost().reply(500);
Chaining is also supported
mock.onGet("/users").reply(200, users).onGet("/posts").reply(200, posts);
.replyOnce() can be used to let the mock only reply once
mock
.onGet("/users")
.replyOnce(200, users) // After the first request to /users, this handler is removed
.onGet("/users")
.replyOnce(500); // The second request to /users will have status code 500
// Any following request would return a 404 since there are
// no matching handlers left
Mocking any request to a given url
// mocks GET, POST, ... requests to /foo
mock.onAny("/foo").reply(200);
.onAny can be useful when you want to test for a specific order of requests
// Expected order of requests:
const responses = [
["GET", "/foo", 200, { foo: "bar" }],
["POST", "/bar", 200],
["PUT", "/baz", 200],
];
// Match ALL requests
mock.onAny().reply((config) => {
const [method, url, ...response] = responses.shift();
if (config.url === url && config.method.toUpperCase() === method)
return response;
// Unexpected request, error out
return [500, {}];
});
Requests that do not map to a mock handler are rejected with a HTTP 404 response. Since
handlers are matched in order, a final onAny() can be used to change the default
behaviour
// Mock GET requests to /foo, reject all others with HTTP 500
mock.onGet("/foo").reply(200).onAny().reply(500);
Mocking a request with a specific request body/data
mock.onPut("/product", { id: 4, name: "foo" }).reply(204);
Using an asymmetric matcher, for example Jest matchers
mock
.onPost(
"/product",
{ id: 1 },
{
headers: expect.objectContaining({
Authorization: expect.stringMatching(/^Basic /),
})
}
)
.reply(204);
Using a custom asymmetric matcher (any object that has a asymmetricMatch property)
mock
.onPost("/product", {
asymmetricMatch: function (actual) {
return ["computer", "phone"].includes(actual["type"]);
},
})
.reply(204);
.passThrough() forwards the matched request over network
// Mock POST requests to /api with HTTP 201, but forward
// GET requests to server
mock
.onPost(/^\/api/)
.reply(201)
.onGet(/^\/api/)
.passThrough();
Recall that the order of handlers is significant
// Mock specific requests, but let unmatched ones through
mock
.onGet("/foo")
.reply(200)
.onPut("/bar", { xyz: "abc" })
.reply(204)
.onAny()
.passThrough();
Note that passThrough requests are not subject to delaying by delayResponse.
If you set onNoMatch option to passthrough all requests would be forwarded over network by default
// Mock all requests to /foo with HTTP 200, but forward
// any others requests to server
const mock = new AxiosMockAdapter(axiosInstance, { onNoMatch: "passthrough" });
mock.onAny("/foo").reply(200);
Using onNoMatch option with throwException to throw an exception when a request is made without match any handler. It's helpful to debug your test mocks.
const mock = new AxiosMockAdapter(axiosInstance, { onNoMatch: "throwException" });
mock.onAny("/foo").reply(200);
axios.get("/unexistent-path");
// Exception message on console:
//
// Could not find mock for:
// {
// "method": "get",
// "url": "http://localhost/unexistent-path"
// }
As of 1.7.0, reply function may return a Promise:
mock.onGet("/product").reply(function (config) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
if (Math.random() > 0.1) {
resolve([200, { id: 4, name: "foo" }]);
} else {
// reject() reason will be passed as-is.
// Use HTTP error status code to simulate server failure.
resolve([500, { success: false }]);
}
}, 1000);
});
});
Composing from multiple sources with Promises:
const normalAxios = axios.create();
const mockAxios = axios.create();
const mock = new AxiosMockAdapter(mockAxios);
mock
.onGet("/orders")
.reply(() =>
Promise.all([
normalAxios.get("/api/v1/orders").then((resp) => resp.data),
normalAxios.get("/api/v2/orders").then((resp) => resp.data),
{ id: "-1", content: "extra row 1" },
{ id: "-2", content: "extra row 2" },
]).then((sources) => [
200,
sources.reduce((agg, source) => agg.concat(source)),
])
);
The history property allows you to enumerate existing axios request objects. The property is an object of verb keys referencing arrays of request objects.
This is useful for testing.
describe("Feature", () => {
it("requests an endpoint", (done) => {
const mock = new AxiosMockAdapter(axios);
mock.onPost("/endpoint").replyOnce(200);
feature
.request()
.then(() => {
expect(mock.history.post.length).toBe(1);
expect(mock.history.post[0].data).toBe(JSON.stringify({ foo: "bar" }));
})
.then(done)
.catch(done.fail);
});
});
You can clear the history with resetHistory
mock.resetHistory();