axios-mock-adapter vs jest-mock-axios vs nock vs sinon
Strategic HTTP Mocking for Frontend Testing Architectures
axios-mock-adapterjest-mock-axiosnocksinonSimilar Packages:

Strategic HTTP Mocking for Frontend Testing Architectures

axios-mock-adapter, jest-mock-axios, nock, and sinon are essential tools for isolating frontend applications from backend services during testing. axios-mock-adapter and jest-mock-axios specifically target the Axios HTTP client, intercepting requests at the adapter level to prevent actual network calls. nock operates at a lower level by intercepting native Node.js HTTP requests, making it framework-agnostic but primarily suited for Node environments. sinon is a comprehensive testing utility that includes spies, stubs, and mocks, offering a built-in fake server for HTTP interception alongside its broader capabilities. These tools enable developers to verify application behavior under various network conditions, error states, and data payloads without relying on fragile end-to-end connections.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
axios-mock-adapter03,54367.9 kB962 years agoMIT
jest-mock-axios025166.4 kB14a year agoMIT
nock013,123185 kB99a month agoMIT
sinon09,7562.52 MB61a month agoBSD-3-Clause

Strategic HTTP Mocking: axios-mock-adapter vs jest-mock-axios vs nock vs sinon

In modern frontend architecture, relying on live backend services for testing is a recipe for flaky builds and slow CI pipelines. We need tools that can simulate network behavior reliably. The ecosystem offers four distinct approaches: two specialized for Axios (axios-mock-adapter, jest-mock-axios), one for low-level Node HTTP interception (nock), and one comprehensive suite (sinon). Let's break down how they work, where they shine, and where they fall short.

🎯 Interception Level: Adapter vs Library vs Network

The most critical difference is where these tools stop the request. Choosing the wrong layer can lead to tests that pass locally but fail in production, or vice versa.

axios-mock-adapter hooks directly into the Axios adapter system. It replaces the internal mechanism Axios uses to send requests. This means the request never leaves the Axios instance.

// axios-mock-adapter: Intercepts at the Axios adapter level
import Axios from 'axios';
import MockAdapter from 'axios-mock-adapter';

const axios = Axios.create();
const mock = new MockAdapter(axios);

mock.onGet('/users').reply(200, { data: ['Alice'] });

// The request is caught inside Axios; no network traffic occurs
const response = await axios.get('/users');

jest-mock-axios replaces the entire Axios module with a Jest mock object. It doesn't just intercept; it simulates the library itself within the Jest environment.

// jest-mock-axios: Replaces the Axios module entirely
import mockAxios from 'jest-mock-axios';
import { fetchData } from './api';

// Setup mock response
mockAxios.get.mockResolvedValue({ data: { id: 1 } });

await fetchData();

// Verify the mock was called
expect(mockAxios.get).toHaveBeenCalledWith('/api/data');

nock operates at the Node.js HTTP layer. It intercepts the actual outgoing HTTP request generated by Node, regardless of whether you used Axios, Fetch, or the native http module. This makes it closer to reality but requires a Node environment.

// nock: Intercepts native Node HTTP requests
import nock from 'nock';
import axios from 'axios';

// Intercept the actual HTTP call to example.com
nock('https://api.example.com')
  .get('/users')
  .reply(200, { data: ['Bob'] });

// Axios makes a real request, but nock catches it at the TCP level
const response = await axios.get('https://api.example.com/users');

sinon provides a "Fake Server" that intercepts XMLHttpRequest (XHR) or Fetch calls depending on configuration. It sits between your code and the browser's network stack (or Node's http module).

// sinon: Uses a fake server to intercept XHR/Fetch
import sinon from 'sinon';
import server from 'sinon/lib/sinon/util/fake-server';

const fakeServer = server.create();
fakeServer.respondWith('GET', '/items', [200, {}, '[{"id": 1}]']);

// Trigger request (e.g., via fetch or XHR)
fetch('/items').then(res => res.json());

// Process the queue
fakeServer.respond();

🧹 State Management and Cleanup

Tests must be isolated. If one test leaves a mock active, it can break the next test. How each library handles cleanup is a major factor in stability.

axios-mock-adapter requires manual reset or restoration. If you forget to call reset() or restore(), mocks persist across tests. This gives you control but increases the risk of human error.

// axios-mock-adapter: Manual cleanup required
beforeEach(() => {
  mock = new MockAdapter(axios);
});

afterEach(() => {
  mock.reset(); // Clears handlers
  // OR mock.restore() to remove the adapter entirely
});

jest-mock-axios integrates tightly with Jest's lifecycle. It offers a specific reset() method that clears history and handlers, designed to be called in afterEach.

// jest-mock-axios: Designed for Jest lifecycle
afterEach(() => {
  mockAxios.reset(); // Clears mock history and handlers
});

nock has a powerful but strict cleaning mechanism. By default, unmatched mocks cause tests to fail. You must explicitly clean up nocks after every test to prevent leakage, often using nock.cleanAll().

// nock: Strict cleanup needed
afterEach(() => {
  nock.cleanAll(); // Removes all intercepted routes
  nock.restore(); // Restores native HTTP behavior if needed
});

sinon uses a sandbox pattern which is highly recommended for automatic cleanup. Creating a sandbox allows you to restore all spies, stubs, and fake servers with a single call.

// sinon: Sandbox pattern for easy cleanup
let sandbox;

beforeEach(() => {
  sandbox = sinon.createSandbox();
  server = sandbox.fakeServer.create();
});

afterEach(() => {
  sandbox.restore(); // Restores everything at once
});

📝 Defining Expectations and Responses

How you describe what should happen varies significantly. Some focus on the URL, others on the function call.

axios-mock-adapter uses a fluent API chained off the HTTP method. It matches URLs (including regex) and allows dynamic responses based on the request config.

// axios-mock-adapter: Fluent URL matching
mock.onGet('/users/123').reply(200, { name: 'John' });

// Dynamic response based on request data
mock.onPost('/login').reply((config) => {
  const data = JSON.parse(config.data);
  if (data.password === 'wrong') {
    return [401, { error: 'Invalid' }];
  }
  return [200, { token: 'abc' }];
});

jest-mock-axios relies on Jest's mock function assertions. You define the resolution value and then assert calls using standard Jest matchers.

// jest-mock-axios: Jest matcher style
mockAxios.post.mockResolvedValue({ data: { token: 'xyz' } });

await login('user', 'pass');

expect(mockAxios.post).toHaveBeenCalledTimes(1);
expect(mockAxios.post).toHaveBeenCalledWith('/login', { user: 'user' });

nock is the most verbose but also the most precise. It validates headers, request bodies, and query parameters before allowing the mock to trigger. If the request doesn't match exactly, nock throws an error.

// nock: Precise request validation
nock('https://api.test.com')
  .post('/login', { username: 'admin' })
  .matchHeader('Authorization', 'Bearer token123')
  .reply(200, { success: true });

sinon responds to requests queued on the fake server. You define a generic responder that checks the request object manually.

// sinon: Manual request inspection
server.respondWith((request) => {
  if (request.method === 'POST' && request.url === '/login') {
    request.respond(200, { 'Content-Type': 'application/json' }, '{"ok": true}');
  } else {
    request.respond(404, {}, '');
  }
});

🌍 Environment Compatibility

Where can you run these tests? This is often the deciding factor for frontend teams.

  • axios-mock-adapter: Works anywhere Axios runs (Browser, Node, React Native). Since it patches the JS library, it is environment-agnostic.
  • jest-mock-axios: Strictly for Jest environments. While Jest can run in various contexts, this package assumes the Jest mock system is present.
  • nock: Node.js only. It hooks into the Node http module. It will not work in a real browser environment (like Cypress running in Chrome or a standard Karma setup). Using it in a browser test will result in silence or errors because the interception layer doesn't exist.
  • sinon: Universal. Works in Node and browsers. However, the "Fake Server" implementation differs slightly between environments (XHR in browsers, http in Node).

⚠️ Critical Limitations and Deprecation Notes

  • nock in Frontend: Do not attempt to use nock for tests that run inside a real browser DOM. It is strictly a Node.js tool. If your test runner executes code in a browser context (e.g., Puppeteer, Playwright, or standard browser Karma), nock will fail to intercept requests.
  • jest-mock-axios Scope: This package only mocks Axios. If your project migrates to fetch or ky in the future, these mocks become useless technical debt. It couples your tests tightly to the Axios implementation.
  • sinon Complexity: While powerful, sinon has a steep learning curve. The Fake Server API is less intuitive than the fluent APIs of axios-mock-adapter or nock. Over-using sinon spies can also slow down test suites if not managed carefully.
  • None are Deprecated: As of the latest checks, all four packages are actively maintained. However, always check the specific version compatibility with your Axios or Jest versions, as major updates sometimes break internal APIs.

📊 Summary: Which One Fits Your Stack?

Featureaxios-mock-adapterjest-mock-axiosnocksinon
Primary TargetAxios usersJest + Axios usersNode.js HTTP clientsGeneral purpose mocking
Interception LayerAxios AdapterModule MockNode HTTP StackXHR / Fetch / HTTP
Browser Support✅ Yes✅ (via Jest)❌ No (Node only)✅ Yes
Setup ComplexityLowVery LowMediumHigh
Request ValidationURL / RegexFunction ArgsHeaders / Body / PathManual Inspection
Best ForComponent/Unit testsQuick Jest unitsIntegration tests (Node)Complex legacy apps

💡 The Architect's Take

If your team is all-in on Axios and runs tests in Jest, axios-mock-adapter offers the best balance of readability and reliability. It simulates the network without the fragility of real HTTP calls, and it works in both Node and browser-based test runners.

Use jest-mock-axios only if you want the absolute simplest setup for quick unit tests and don't care about verifying exact HTTP headers or structures. It's fast but shallow.

Reach for nock if you are writing integration tests in Node (e.g., testing API wrappers or server-side rendering logic) where verifying the exact HTTP contract is critical. It ensures your code speaks the correct HTTP protocol, not just that the Axios function was called.

Finally, choose sinon if you have a mixed environment (legacy XHR, new Fetch, custom HTTP clients) and need a single tool to handle spies, stubs, and network fakes together. It is the heavy-duty option for complex, heterogeneous codebases.

Final Rule: Never use nock for browser-based unit tests. Stick to adapter-level mocking (axios-mock-adapter) for component testing to keep your suite fast and deterministic.

How to Choose: axios-mock-adapter vs jest-mock-axios vs nock vs sinon

  • axios-mock-adapter:

    Choose axios-mock-adapter if your project relies heavily on Axios and you need a simple, promise-based way to define mock responses directly in your test files. It is ideal for unit and integration tests where you want to avoid network overhead entirely and define specific request/response pairs with minimal setup. This package is particularly strong when you need to test complex Axios configurations like interceptors or custom adapters.

  • jest-mock-axios:

    Choose jest-mock-axios if you are strictly using Jest and want a lightweight utility that resets mocks automatically between tests to prevent state leakage. It is best suited for projects that prefer a dedicated mock object over an adapter wrapper and need quick assertions on whether specific Axios methods were called. Avoid this if you use other test runners or need to mock non-Axios HTTP clients.

  • nock:

    Choose nock if you need to test code that uses the native Node.js http or https modules, or if you want to verify that the actual HTTP request structure (headers, body, path) matches expectations before returning a mock. It is the standard choice for integration testing in Node environments where you want to simulate a real server's behavior without spinning one up. Do not use this in browser-only environments as it relies on Node-specific internals.

  • sinon:

    Choose sinon if you need a unified toolkit for spying, stubbing, and mocking across your entire application, not just HTTP requests. Its fake server feature is useful if you need to handle multiple types of XHR/fetch requests in a legacy browser environment or want to combine HTTP mocking with deep function instrumentation. It is the right choice when your testing strategy requires complex interaction verification beyond simple response mocking.

README for axios-mock-adapter

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