Mocking Tools: MSW, nock, sinon, WireMock — Service Virtualization
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.
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.