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 modificar módulos o arrancar un servidor de pruebas, MSW declara request handlers que devuelven respuestas mock. Los mismos handlers funcionan en desarrollo, tests y Storybook. Si solo necesitas stubs HTTP simples sin la semántica de Service Worker, mira la receta de API mocking.
Cuándo Usar
- Mockear respuestas de API durante desarrollo frontend sin un backend
- Probar componentes que obtienen datos con
fetch,axioso@tanstack/react-query - Probar queries y mutations de GraphQL sin un servidor GraphQL corriendo
- Compartir mocks entre Storybook, pruebas unitarias y de integración
- Desarrollar funcionalidades frontend antes de que la API del backend esté lista
Cuándo No Usar
- Probar tu propia API backend — usa
supertestoWebTestClient - Probar comportamiento de red real — MSW intercepta antes de la capa de red
- Pruebas de carga — MSW añade overhead de intercepción
- Probar conexiones WebSocket — MSW 2.x lo soporta, pero es menos maduro que el mocking de HTTP
Solución
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)
El mismo patrón funciona con Jest — solo cambian los nombres de los hooks de ciclo de vida. Para flujos de Vitest orientados a snapshots, mira Snapshot Testing con 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();
});
Sobrescribir 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 });
});
Explicación
MSW no simula tu cliente HTTP — se interpone por debajo de él. Por eso fetch, axios y @tanstack/react-query funcionan sin configuración: todos pasan por los mismos puntos de intercepción.
En el navegador, msw init public/ genera un mockServiceWorker.js que setupWorker registra. El worker intercepta las peticiones por el evento fetch, así que DevTools las muestra como llamadas reales — puedes inspeccionar headers y payloads como siempre. En Node.js no existe la API de Service Worker, así que msw/node parchea fetch, http, https y XMLHttpRequest directamente.
Sobre la resolución de handlers hay una regla clave: los handlers registrados con server.use() se anteponen a la lista, así que un override por test siempre gana al array handlers compartido, y resetHandlers() en afterEach elimina esos overrides para que nunca se filtren entre tests.
¿Migra desde MSW 1.x? La API cambió bastante: rest.get() con ctx.json() pasó a ser http.get() devolviendo HttpResponse.json(), res() desapareció, y el mocking de WebSocket se movió a ws.link(). Todos los ejemplos de esta receta usan la API 2.x.
Variantes
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 },
};
Para una integración más fina, msw-storybook-addon ofrece initialize() y un mswLoader que permite que cada historia sobrescriba handlers de forma declarativa en lugar de arrancar el worker a mano.
Mejores Prácticas
- Para una guía más completa, mira Interceptar Servicios HTTP Externos con WireMock — el equivalente en JVM para stubs de servicios HTTP.
- Comparte handlers entre tests, Storybook y desarrollo — una fuente de verdad para los datos mock
- Usa
server.resetHandlers()enafterEachpara limpiar los overrides por test - Configura
onUnhandledRequest: "error"en tests para atrapar peticiones sin mockear - Usa
delay()para probar estados de carga y manejo de timeouts - Guarda los handlers en un archivo
handlers.tsseparado para reutilizarlos - Usa
server.use()para overrides puntuales en lugar de modificar los handlers compartidos
Errores Comunes
- 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.
See Also
- API Mocking
- Snapshot Testing con Vitest
- Guía de Estrategia de Testing
- Documentación de MSW
- Archivos complementarios — handlers, setup de Vitest y configuración de Storybook ejecutables
Preguntas frecuentes
¿Cómo mockeo subidas de archivos?
http.post("/api/upload", async ({ 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 depuro peticiones sin mockear?
Configura onUnhandledRequest: "warn" para ver avisos en consola, o "error" para que los tests fallen ante peticiones sin mockear. Revisa la consola para ver la URL completa y el 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.
GuideGuía de Estrategia de Testing
Una guía práctica para construir una estrategia de testing en capas con unit, integration y end-to-end tests.