Introduction

Mocking is essential for isolated testing. The right mocking strategy depends on what you are testing: frontend components that make HTTP calls, backend services with external dependencies, or complex interactions between multiple services. This article covers four complementary mocking approaches.

Mocking Tools: MSW, nock, sinon, WireMock — Service Virtualization

MSW (Mock Service Worker)

MSW intercepts network requests at the service worker level, working in both browser and Node.js:

// mocks/handlers.ts

import { http, HttpResponse } from "msw";

export const handlers = [

// REST API handler

http.get("https://api.example.com/users", ({ request }) => {

const url = new URL(request.url);

const page = url.searchParams.get("page") || "1";

return HttpResponse.json({

users: [

{ id: 1, name: "Alice" },

{ id: 2, name: "Bob" },

],

total: 50,

page: Number(page),

});

}),

// POST handler with request validation

http.post("https://api.example.com/users", async ({ request }) => {

const body = await request.json();

if (!body.name) {

return HttpResponse.json(

{ error: "Name is required" },

{ status: 400 }

);

}

return HttpResponse.json(

{ id: 3, name: body.name },

{ status: 201 }

);

}),

// GraphQL handler

http.post("https://api.example.com/graphql", async ({ request }) => {

const { query } = await request.json();

if (query.includes("currentUser")) {

return HttpResponse.json({

data: { currentUser: { id: 1, name: "Alice", role: "admin" } },

});

}

}),

];

// test setup

import { setupServer } from "msw/node";

import { handlers } from "./handlers";

const server = setupServer(...handlers);

beforeAll(() => server.listen());

afterEach(() => server.resetHandlers());

afterAll(() => server.close());

// Override handler for specific test

test("handles network error", async () => {

server.use(

http.get("https://api.example.com/users", () => {

return HttpResponse.error();

})

);

// Test error handling logic

});

Strengths : Works at the network level (not module level), browser and Node.js support, realistic interception, first-class GraphQL support.

nock

nock intercepts HTTP requests at the Node.js http module level:

const nock = require("nock");

// Mock a GET request

nock("https://api.example.com")

.get("/users")

.query({ page: 1, limit: 10 })

.reply(200, {

users: [{ id: 1, name: "Alice" }],

total: 1,

});

// Mock with dynamic response

nock("https://api.example.com")

.post("/users", (body) => body.name && body.email)

.reply(201, (uri, requestBody) => {

const body = JSON.parse(requestBody);

return { id: Date.now(), ...body, createdAt: new Date().toISOString() };

});

// Mock multiple times with different responses

nock("https://api.example.com")

.get("/status")

.times(3)

.reply(200, { status: "ok" });

// Persist mock for repeated calls

nock("https://api.example.com")

.get("/health")

.times(Infinity)

.reply(200, { healthy: true });

// Scope assertion

const scope = nock("https://api.example.com")

.get("/users")

.reply(200, []);

// After test, verify all mocked endpoints were called

expect(scope.isDone()).toBe(true);

nock.cleanAll();

Strengths : Fine-grained request matching, supports regex URL matching, response templating, scope isolation.

Weaknesses : Node.js only, module-level interception (not browser), can be slow with many mocks.

Sinon

Sinon provides standalone test doubles (spies, stubs, mocks):

const sinon = require("sinon");

// Spy: observe function calls

const spy = sinon.spy();

spy("hello", "world");

console.log(spy.calledOnce); // true

console.log(spy.args[0]); // ["hello", "world"]

// Stub: replace function behavior

const stub = sinon.stub();

stub.returns(42);

stub.withArgs("special").returns(100);

console.log(stub("anything")); // 42

console.log(stub("special")); // 100

// Stub an object method

const api = { fetch: async (url) => ({ data: [] }) };

const fetchStub = sinon.stub(api, "fetch");

fetchStub.resolves({ data: [{ id: 1 }] });

fetchStub.rejects(new Error("Network error"));

// Restore original after test

fetchStub.restore();

// Timers: control time-dependent code

const clock = sinon.useFakeTimers();

let callback = sinon.spy();

setTimeout(callback, 1000);

clock.tick(1000);

expect(callback.calledOnce).toBe(true);

clock.restore();

Strengths : Rich assertion API, fake timers, standalone (framework-agnostic), excellent for module-level mocking.

WireMock

WireMock runs as a standalone HTTP server, perfect for integration tests:

// Java example (WireMock also has HTTP API)

import static com.github.tomakehurst.wiremock.client.WireMock.*;

// Start WireMock server

WireMockServer wireMockServer = new WireMockServer(8089);

wireMockServer.start();

// Stub a GET endpoint

stubFor(get(urlEqualTo("/api/users/1"))

.willReturn(aResponse()

.withStatus(200)

.withHeader("Content-Type", "application/json")

.withBody("{ \"id\": 1, \"name\": \"Alice\" }")));

// Stub with response templating

stubFor(post(urlEqualTo("/api/users"))

.willReturn(aResponse()

.withStatus(201)

.withBody("{{request.body}}")

.withTransformers("response-template")));

// Verify request was made

verify(getRequestedFor(urlPathEqualTo("/api/users/1"))

.withHeader("Authorization", containing("Bearer")));

Start WireMock standalone

java -jar wiremock-standalone.jar --port 8089 --verbose

Configure via JSON files in mappings/ directory

__files/response.json contains the response body

Comparison

| Feature | MSW | nock | Sinon | WireMock |

|---------|-----|------|-------|----------|

| Level | Network (SW) | Network (http) | Function | HTTP server |

| Browser | Yes | No | Yes | No |

| Node.js | Yes | Yes | Yes | Yes |

| Real HTTP | Yes | No (hijacked) | No | Yes |

| Setup complexity | Medium | Low | Low | Medium |

| Best for | Frontend tests | Backend tests | Unit tests | Integration tests |

Recommendations

  • Frontend API mocking : MSW is the best choice — it works in both test and development environments.

  • Backend HTTP mocking : nock for simple cases, WireMock for complex integration tests.

  • Function-level mocking : Sinon for spying, stubbing, and fake timers.

  • Integration/E2E tests : WireMock as a standalone HTTP server for contract testing.

Use MSW + Sinon as your core mocking stack. Add nock or WireMock when testing service-to-service HTTP interactions at the integration level.