MSW — simular APIs en navegador y Node
MSW (Mock Service Worker) intercepta solicitudes HTTP con handlers declarativos. En navegador utiliza un Service Worker; en Node intercepta la red del proceso. El código de aplicación continúa usando fetch, Axios u otro cliente real, por lo que la prueba conserva URL, método, headers, serialización y manejo de respuesta.
Instalación
Sección titulada «Instalación»pnpm add -D mswbun add -d mswnpm install -D mswCuándo usarlo
Sección titulada «Cuándo usarlo»| Necesidad | MSW | Alternativa |
|---|---|---|
| componente consume una API | sí | mockear hook oculta HTTP |
forzar 500, 429 o demora | sí | proveedor real no es determinista |
| probar una query SQL | no | base real/Testcontainers |
| confirmar contrato externo real | parcialmente | sandbox o contract test |
| probar backend propio en E2E | normalmente no | entorno desplegado real |
MSW simula la respuesta, no demuestra que el servidor real la produzca. Combínalo con contratos e integración del backend.
Handler compartido
Sección titulada «Handler compartido»import { HttpResponse, http } from "msw"
export const handlers = [ http.get("/api/users/:id", ({ params }) => { return HttpResponse.json({ id: params.id, displayName: "Ana" }) })]Un handler describe el protocolo. Mantén respuestas pequeñas y válidas; crea overrides por prueba para el caso que cambia.
Configuración en Node con Vitest
Sección titulada «Configuración en Node con Vitest»import { setupServer } from "msw/node"import { handlers } from "./handlers"
export const server = setupServer(...handlers)import { afterAll, afterEach, beforeAll } from "vitest"import { server } from "./mocks/server"
beforeAll(() => server.listen({ onUnhandledRequest: "error" }))afterEach(() => server.resetHandlers())afterAll(() => server.close())onUnhandledRequest: 'error' evita que una prueba haga red real por accidente. resetHandlers elimina overrides temporales y conserva los handlers iniciales.
Probar éxito y error
Sección titulada «Probar éxito y error»it("muestra el perfil recibido", async () => { render(<UserProfile userId="u_1" />)
expect(await screen.findByRole("heading", { name: "Ana" })).toBeVisible()})it("permite reintentar después de un error", async () => { server.use( http.get("/api/users/:id", () => { return HttpResponse.json({ code: "TEMPORARY_FAILURE" }, { status: 503 }) }) )
const user = userEvent.setup() render(<UserProfile userId="u_1" />)
expect(await screen.findByRole("alert")).toHaveTextContent( /intenta de nuevo/i ) await user.click(screen.getByRole("button", { name: /reintentar/i }))})El caso todavía necesita decidir qué respuesta tendrá el segundo intento. Puedes usar un contador local en el handler o reemplazarlo antes del clic; mantén esa secuencia explícita.
Inspeccionar request
Sección titulada «Inspeccionar request»http.post("/api/orders", async ({ request }) => { const body = (await request.json()) as { productId?: string } const authorization = request.headers.get("authorization")
if (!authorization) { return HttpResponse.json({ code: "UNAUTHORIZED" }, { status: 401 }) }
if (!body.productId) { return HttpResponse.json({ code: "INVALID_BODY" }, { status: 400 }) }
return HttpResponse.json({ id: "order_1" }, { status: 201 })})No dupliques todo el backend dentro del handler. Comprueba únicamente aspectos necesarios para que un request incorrecto no reciba éxito automáticamente.
Latencia, red y respuestas inválidas
Sección titulada «Latencia, red y respuestas inválidas»import { HttpResponse, delay, http } from "msw"
server.use( http.get("/api/search", async () => { await delay(2000) return HttpResponse.json({ results: [] }) }))Prueba carga, cancelación, timeout, JSON inválido y status inesperados. Evita demoras reales largas en toda la suite; usa timers o una demora corta cuando solo importa el estado de carga.
Navegador y Storybook
Sección titulada «Navegador y Storybook»Los mismos handlers pueden alimentar desarrollo local, historias y pruebas. En navegador se genera un worker público y se inicia antes de la aplicación. No habilites mocks accidentalmente en producción; condiciona el import al entorno y comprueba el bundle.
Errores frecuentes
Sección titulada «Errores frecuentes»- permitir request no manejado y llamar Internet desde CI;
- handler global que filtra estado entre tests;
- respuesta que no coincide con el contrato real;
- recrear lógica completa del servidor;
- interceptar el backend que un E2E pretende verificar;
- usar datos sensibles copiados de producción.