Mockear Peticiones de Red con MSW
Cómo usar Mock Service Worker (MSW) para interceptar peticiones de red en tests y desarrollo de JavaScript, incluyendo mocking de REST y GraphQL.
Overview
Mock Service Worker (MSW) intercepta peticiones de red a nivel Service Worker en navegadores y a nivel fetch/http en Node.js. En lugar de patchear módulos o arrancar un mock server, MSW declara request handlers que devuelven respuestas mock. Los mismos handlers funcionan en desarrollo, tests y Storybook.
When to Use
- Mockear respuestas de API durante desarrollo frontend sin un backend
- Testear componentes que fetchean datos con
fetch,axioso@tanstack/react-query - Testear queries y mutations de GraphQL sin un servidor GraphQL corriendo
- Compartir mocks entre Storybook, unit tests e integration tests
- Desarrollar features frontend antes de que la API del backend esté lista
When NOT to Use
- Testear tu propia API backend — usa
supertestoWebTestClient - Testear comportamiento de red real — MSW intercepta antes de la capa de red
- Load testing — MSW añade overhead de intercepción
- Testear conexiones WebSocket — MSW 2.x lo soporta, pero es menos maduro que HTTP mocking
Solution
Setup
npm install -D msw
Handlers REST básicos
import { http, HttpResponse } from "msw";
export const handlers = [
http.get("https://api.example.com/users/:id", ({ params }) => {
return HttpResponse.json({
id: Number(params.id),
name: "Alice",
email: "alice@example.com",
});
}),
http.post("https://api.example.com/users", async ({ request }) => {
const body = await request.json();
return HttpResponse.json(
{ id: 42, ...body },
{ status: 201 },
);
}),
http.get("https://api.example.com/users", ({ request }) => {
const url = new URL(request.url);
const page = url.searchParams.get("page") || "1";
return HttpResponse.json({
data: [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }],
page: Number(page),
total: 2,
});
}),
];
Usar MSW en tests de Node.js (Vitest)
import { setupServer } from "msw/node";
import { afterAll, afterEach, beforeAll, describe, it, expect } from "vitest";
import { handlers } from "./handlers";
const server = setupServer(...handlers);
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
describe("User API", () => {
it("fetches a user by ID", async () => {
const response = await fetch("https://api.example.com/users/1");
const user = await response.json();
expect(user.id).toBe(1);
expect(user.name).toBe("Alice");
});
it("creates a user", async () => {
const response = await fetch("https://api.example.com/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Charlie", email: "charlie@example.com" }),
});
expect(response.status).toBe(201);
const user = await response.json();
expect(user.id).toBe(42);
expect(user.name).toBe("Charlie");
});
});
Handlers de GraphQL
import { graphql, HttpResponse } from "msw";
export const graphqlHandlers = [
graphql.query("GetUser", ({ variables }) => {
return HttpResponse.json({
data: {
user: {
id: variables.id,
name: "Alice",
email: "alice@example.com",
},
},
});
}),
graphql.mutation("CreateUser", ({ variables }) => {
return HttpResponse.json({
data: {
createUser: {
id: 99,
...variables.input,
},
},
});
}),
];
Simular errores
http.get("https://api.example.com/users/1", () => {
return HttpResponse.json(
{ error: "Not found" },
{ status: 404 },
);
});
http.get("https://api.example.com/server-error", () => {
return HttpResponse.json(
{ error: "Internal Server Error" },
{ status: 500 },
);
});
Simular errores de red
import { http, HttpResponse, delay } from "msw";
http.get("https://api.example.com/slow", async () => {
await delay(5000);
return HttpResponse.json({ data: "delayed" });
});
http.get("https://api.example.com/network-error", () => {
return HttpResponse.error();
});
Override de handlers por test
it("handles 500 error", async () => {
server.use(
http.get("https://api.example.com/users/1", () => {
return HttpResponse.json({ error: "Server error" }, { status: 500 });
}),
);
const response = await fetch("https://api.example.com/users/1");
expect(response.status).toBe(500);
});
Usar MSW en el navegador (desarrollo)
import { setupWorker } from "msw/browser";
import { handlers } from "./handlers";
const worker = setupWorker(...handlers);
await worker.start({
onUnhandledRequest: "bypass",
});
console.log("MSW worker started");
Genera el archivo service worker:
npx msw init public/ --save
Verificar peticiones
import { http, HttpResponse } from "msw";
let lastRequestBody: unknown;
export const captureHandler = http.post(
"https://api.example.com/orders",
async ({ request }) => {
lastRequestBody = await request.json();
return HttpResponse.json({ id: 1 }, { status: 201 });
},
);
// En test
it("sends correct order payload", async () => {
await submitOrder({ productId: 10, quantity: 3 });
expect(lastRequestBody).toEqual({ productId: 10, quantity: 3 });
});
Variants
Usar MSW con React Testing Library
import { render, screen, waitFor } from "@testing-library/react";
import { UserProfile } from "./UserProfile";
it("displays user data from API", async () => {
server.use(
http.get("/api/users/1", () => {
return HttpResponse.json({ name: "Alice", email: "alice@example.com" });
}),
);
render(<UserProfile userId={1} />);
await waitFor(() => {
expect(screen.getByText("Alice")).toBeInTheDocument();
});
});
Usar MSW con Storybook
// .storybook/preview.tsx
import { setupWorker } from "msw/browser";
import { handlers } from "../src/mocks/handlers";
const worker = setupWorker(...handlers);
await worker.start();
export const parameters = {
msw: { handlers },
};
Best Practices
-
For a deeper guide, see Stub External HTTP Services with WireMock.
-
Comparte handlers entre tests, Storybook y desarrollo — una fuente de verdad para mock data
-
Usa
server.resetHandlers()enafterEachpara limpiar overrides por test -
Setea
onUnhandledRequest: "error"en tests para atrapar peticiones sin mockear -
Usa
delay()para testear loading states y timeout handling -
Guarda handlers en un archivo
handlers.tsseparado para reusabilidad -
Usa
server.use()para overrides one-off en lugar de modificar handlers compartidos
Common Mistakes
- No llamar
server.listen()enbeforeAll: MSW no intercepta peticiones hasta que el servidor arranca. - No resetear handlers: los overrides por test filtran a tests subsecuentes. Siempre llama
resetHandlers(). - Mockear URLs relativas en tests de Node.js: usa URLs absolutas o configura una base URL. MSW en Node.js necesita URLs completas.
- Olvidar
awaiten handlers async:HttpResponse.json()es sincrónico, pero leer el request body conrequest.json()es async. - No cerrar el servidor: llama
server.close()enafterAllpara evitar que el proceso se cuelgue.
Preguntas frecuentes
¿Cómo mockeo file uploads?
http.post("/api/upload", ({ request }) => {
const formData = await request.formData();
const file = formData.get("file") as File;
return HttpResponse.json({ filename: file.name, size: file.size });
});
¿Puedo usar MSW con axios?
Sí. MSW intercepta a nivel fetch y http, que axios usa por debajo. Sin configuración adicional.
¿Cómo mockeo respuestas paginadas?
http.get("/api/users", ({ request }) => {
const url = new URL(request.url);
const page = Number(url.searchParams.get("page") || 1);
const allUsers = Array.from({ length: 50 }, (_, i) => ({ id: i + 1 }));
const perPage = 10;
const start = (page - 1) * perPage;
return HttpResponse.json({
data: allUsers.slice(start, start + perPage),
page,
total: 50,
});
});
¿MSW funciona con WebSocket?
MSW 2.x soporta mocking de WebSocket via handler ws:
import { ws } from "msw";
const chat = ws.link("wss://api.example.com/chat");
export const handlers = [
chat.addEventListener("connection", ({ client }) => {
client.send("Welcome!");
}),
];
¿Cómo debuggeo peticiones sin mockear?
Setea onUnhandledRequest: "warn" para ver warnings en consola, o "error" para fallar tests en peticiones sin mockear. Revisa el output de consola para la URL completa y método.
Recursos Relacionados
Vitest Snapshot Testing para React
Cómo usar Vitest snapshot testing para detectar cambios no intencionados en la UI de componentes React, incluyendo inline snapshots y flujos de actualización.
RecipeTestear APIs Express con supertest
Cómo testear endpoints REST de Express.js end-to-end usando supertest, incluyendo códigos de estado, bodies JSON, headers, autenticación y manejo de errores.
RecipeAPI Mocking para Testing
Construye tests confiables mockeando APIs externas con WireMock, MockServer y MSW para eliminar flakiness y testear casos edge.