axios-mock-adapter vs fetch-mock vs msw vs nock
HTTP リクエストモックツールの選定:ユニットテストから E2E まで
axios-mock-adapterfetch-mockmswnock類似パッケージ:

HTTP リクエストモックツールの選定:ユニットテストから E2E まで

axios-mock-adapter、fetch-mock、msw、nock は、JavaScript アプリケーションにおける HTTP リクエストをシミュレートするための主要なライブラリです。これらは、外部 API に依存せずにテストを実行したり、バックエンドが未完成の状態でフロントエンドの開発を進めたりするために不可欠なツールです。

axios-mock-adapter は、人気のある HTTP クライアントである Axios に特化して設計されており、Axios の内部アダプターをフックしてリクエストをインターセプトします。一方、fetch-mock はネイティブの Fetch API をグローバルに置き換えることで動作し、ブラウザ環境での Fetch 利用を前提としています。

msw (Mock Service Worker) は、Service Worker を利用してネットワークレベルでリクエストを横取りする全く異なるアプローチを採用しています。これにより、アプリケーションコードの変更なしに、ブラウザと Node.js の両方で同じモック定義を再利用できます。

最後に nock は、Node.js 環境に特化した老舗のライブラリで、HTTP クライアントを直接モックするのではなく、Node の HTTP モジュールレベルで外部サーバーへの実際の接続をフックします。主にサーバーサイドのテストや統合テストで使用されます。

npmのダウンロードトレンド

3 年

GitHub Starsランキング

統計詳細

パッケージ
ダウンロード数
Stars
サイズ
Issues
公開日時
ライセンス
axios-mock-adapter03,54467.9 kB962年前MIT
fetch-mock01,309157 kB410ヶ月前MIT
msw018,1786.05 MB412ヶ月前MIT
nock013,122185 kB981ヶ月前MIT

HTTP モックツールの深層比較:axios-mock-adapter, fetch-mock, msw, nock

現代の Web 開発において、外部 API との通信を伴うコードをテストすることは避けて通れない課題です。ネットワークの状態に左右されず、高速かつ安定したテストを実行するためには、HTTP リクエストを適切にモック(偽装)する必要があります。axios-mock-adapter、fetch-mock、msw、nock はいずれもこの目的を果たしますが、そのアプローチと適したシナリオは大きく異なります。

本記事では、これら 4 つのライブラリを技術的な観点から比較し、実際のコード例を通じてそれぞれの特徴と使い分けを解説します。

🏗️ アーキテクチャと動作原理の違い

まず、これらが「どこで」「どのように」リクエストを傍受しているかを理解することが重要です。

axios-mock-adapter は、Axios ライブラリの内部アダプターを置き換えます。Axios が HTTP リクエストを送ろうとした瞬間に横取りし、定義されたレスポンスを返します。そのため、実際のネットワーク通信は一切発生しません。

fetch-mock は、グローバルな fetch 関数そのものを置き換えます。ブラウザや Node.js 環境において、コードが fetch() を呼び出すと、本来の機能ではなくこのライブラリのロジックが実行されます。

msw (Mock Service Worker) は、Service Worker というブラウザの機能(および Node.js でのエミュレーション)を利用します。アプリケーションとネットワークの間に介入するため、コード側は「実際の API を叩いている」と錯覚します。これが最大の強みで、アプリケーションコードを一切変更せずにモックを適用できます。

nock は、Node.js の http / https モジュールレベルでフックします。Axios や Fetch などが内部で Node の HTTP モジュールを使用するため、結果としてすべての outgoing HTTP リクエストを捕捉できます。これは純粋に Node.js 環境限定の技術です。

🛠️ 基本的なセットアップと構文

各ライブラリがどのように定義されるかを見てみましょう。

axios-mock-adapter

Axios インスタンスを渡して初期化し、チェーン構文でルールを定義します。

import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';

const mock = new MockAdapter(axios);

// GET リクエストのモック
mock.onGet('/users').reply(200, [
  { id: 1, name: 'Alice' }
]);

fetch-mock

グローバルな fetch をターゲットに設定します。

import fetchMock from 'fetch-mock';

// GET リクエストのモック
fetchMock.get('/users', {
  body: [{ id: 1, name: 'Alice' }],
  status: 200
});

msw

Service Worker を登録し、ハンドラーを定義します。これが唯一、非同期で初期化が必要なケースです。

import { setupServer } from 'msw/node';
import { http, HttpResponse } from 'msw';

const handlers = [
  http.get('/users', () => {
    return HttpResponse.json([
      { id: 1, name: 'Alice' }
    ]);
  })
];

const server = setupServer(...handlers);

// テスト前後で起動・停止
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

nock

ベース URL を指定し、パスとメソッドをチェーンで定義します。

import nock from 'nock';
import axios from 'axios';

// ベース URL を指定
nock('https://api.example.com')
  .get('/users')
  .reply(200, [
    { id: 1, name: 'Alice' }
  ]);

// この後、axios.get('https://api.example.com/users') を呼ぶとモックされる

🔄 動的レスポンスとロジックの扱い

静的なデータだけでなく、リクエストの内容に応じてレスポンスを変えたい場合があります。

axios-mock-adapter

コールバック関数を渡すことで、リクエストオブジェクトに基づいて動的にレスポンスを生成できます。

mock.onGet('/users').reply((config) => {
  const { token } = config.headers;
  if (token === 'invalid') {
    return [401, { error: 'Unauthorized' }];
  }
  return [200, { data: 'success' }];
});

fetch-mock

関数を返すことで同様の処理が可能です。

fetchMock.get('/users', (url, opts) => {
  const token = opts.headers?.token;
  if (token === 'invalid') {
    return { status: 401, body: { error: 'Unauthorized' } };
  }
  return { status: 200, body: { data: 'success' } };
});

msw

ハンドラー関数内で req オブジェクトを自由に操作できます。これが最も柔軟性が高いです。

http.get('/users', ({ request }) => {
  const token = request.headers.get('token');
  if (token === 'invalid') {
    return HttpResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }
  return HttpResponse.json({ data: 'success' });
});

nock

関数を渡して動的なレスポンスを生成できます。

nock('https://api.example.com')
  .get('/users')
  .reply(200, (uri, requestBody) => {
    // requestBody や uri を使って動的にデータを生成
    return { data: 'success', path: uri };
  });

🌍 環境依存性:ブラウザ vs Node.js

この選定において最も重要な要素の一つが、どこでテストを実行するかです。

  • axios-mock-adapter: ブラウザ・Node.js 両方で動作しますが、Axios が必須です。Fetch API には対応していません。
  • fetch-mock: 本来はブラウザ向けですが、Node.js でも global.fetch をポリフィルすれば動作します。ただし、設定の手間がかかります。
  • msw: ブラウザと Node.js で同じコードが動きます。Service Worker をブラウザで使用し、Node.js では内部エミュレーターを使用するため、ユニバーサルなテストスイートに最適です。
  • nock: Node.js 専用です。ブラウザのバンドルに含めようとするとエラーになります。サーバーサイドの統合テストに特化しています。

⚠️ エラーハンドリングとタイムアウトのシミュレーション

ネットワークエラーやタイムアウトを再現する能力も重要です。

axios-mock-adapter

ネットワークエラーをシミュレートするには、Promise を reject させる関数を返します。

mock.onGet('/users').reply(() => {
  return new Promise(() => {
    // 解決しないことでタイムアウトを模倣するか、reject を呼ぶ
    throw new Error('Network Error');
  });
});
// または単純に
mock.onGet('/users').networkError();

fetch-mock

throws オプションや、reject する Promise を返すことで表現します。

fetchMock.get('/users', 500); // ステータスコード
// または
fetchMock.get('/users', { throws: new TypeError('Failed to fetch') });

msw

networkError ヘルパーが用意されており、非常に直感的です。

import { http, HttpResponse } from 'msw';

http.get('/users', () => {
  return HttpResponse.error(); // ネットワークエラーを発生させる
});

nock

replyWithError メソッドが用意されています。

nock('https://api.example.com')
  .get('/users')
  .replyWithError('Connection refused');

🧪 テストの隔離とクリーンアップ

テスト間で状態が漏れないようにすることは、安定したテスト_suite_に不可欠です。

  • axios-mock-adapter: mock.reset() または mock.restore() を呼び出す必要があります。忘记すると、他のテストに影響します。
  • fetch-mock: fetchMock.restore() でグローバルな状態を元に戻す必要があります。
  • msw: server.resetHandlers() でランタイムのハンドラーをリセットし、server.close() で停止します。この明示的なライフサイクル管理が、信頼性の高いテストを支えています。
  • nock: nock.cleanAll() でインターセプターを削除します。また、nock.isActive() を使って未消費のモックがないか確認する機能もあり、厳密なテストに向いています。

💡 実戦での使い分けガイド

それぞれの強みを踏まえ、以下のような基準で選定することをお勧めします。

  1. フロントエンドのユニットテストで Axios を使っている → axios-mock-adapter が最も手っ取り早く、設定も最小限で済みます。

  2. Fetch API を使っており、ブラウザ環境のみでテストする → fetch-mock が妥当ですが、将来的な拡張性を考えると msw への移行を検討する価値があります。

  3. 開発環境全体(ブラウザ・Node)で統一されたモックを使いたい / API サーバーの代わりが欲しい → msw が唯一無二の選択肢です。特に、バックエンドが未完成の状態でフロントエンドを開発する際に絶大な威力を発揮します。

  4. Node.js でサーバーサイドロジックや外部 API 連携をテストする → nock が最強のツールです。クライアントライブラリに依存せず、実際の HTTP レベルで検証できるため、信頼性が極めて高いです。

📊 比較サマリー

特徴axios-mock-adapterfetch-mockmswnock
対象クライアントAxios のみFetch API任意 (ネットワーク層)任意 (Node HTTP)
動作環境Browser / NodeBrowser / Node*Browser / NodeNode のみ
アプローチアダプター置換グローバル関数置換Service WorkerHTTP モジュールフック
コード変更必要 (インスタンス登録)必要 (global 置換)不要不要
API 定義の共有困難困難容易困難
学習コスト低中中〜高中

* Node.js ではポリフィルが必要

🎯 結論

「どれが最高か」という問いに対する唯一の正解はありません。しかし、現代のフロントエンドアーキテクチャにおいては、msw が提供する「ネットワークレベルのモック」と「環境非依存性」は、開発体験を大きく向上させる可能性を秘めています。特に、モック定義をテストと開発サーバーの両方で共有できる点は、チーム全体の生産性を高めます。

一方、既存の Axios ベースのプロジェクトで手軽にテストを書きたいなら axios-mock-adapter は依然として有効です。また、サーバーサイドの堅牢なテストには nock が欠かせません。

プロジェクトの技術スタックとテストの目的に合わせて、これらのツールを適切に使い分けることが、質の高いアプリケーション開発への近道となります。

選び方: axios-mock-adapter vs fetch-mock vs msw vs nock

  • axios-mock-adapter:

    プロジェクトで Axios を既に使用しており、ユニットテスト内でシンプルにリクエストとレスポンスを定義したい場合に最適です。セットアップが非常に簡単で、Axios インスタンスに直接紐付けて使用できるため、小規模なテストスイートや特定のコンポーネントの隔離テストに向いています。ただし、Axios に依存しているため、Fetch API や他のクライアントを使う場合には利用できません。

  • fetch-mock:

    ネイティブの Fetch API を使用しているプロジェクトで、ブラウザ環境でのみテストを実行する場合に適しています。グローバルな Fetch 関数を置き換えるため、実装詳細に依存しないテストが可能です。しかし、Node.js 環境での実行には追加の設定(global-fetch のポリフィルなど)が必要になることが多く、最近ではより汎用性の高い MSW に移行するケースが増えています。

  • msw:

    フロントエンドとバックエンドの両方で同じモック定義を共有したい場合や、アプリケーションコードを変更せずにネットワーク層をモックしたい場合に最適な選択肢です。Service Worker を使用するため、実際のネットワークリクエストと同様の挙動を再現でき、開発中の API サーバーの代わりとしても機能します。学習コストはやや高いですが、長期的な保守性と開発体験の向上が見込めます。

  • nock:

    Node.js 環境でのサーバーサイドテストや、外部 API との統合テストを実行する場合に強力なツールです。HTTP クライアントの種類(Axios, Fetch, request など)を問わず、出力される HTTP リクエストをインターセプトできるため、ライブラリに依存しないテストが可能です。ただし、ブラウザ環境では動作しないため、フロントエンドのユニットテストには使用できません。

axios-mock-adapter のREADME

axios-mock-adapter

Axios adapter that allows to easily mock requests

Installation

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.

Example

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)),
    ])
  );

History

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();