learning-front

Nivel 8 · Calidad: que no se rompa en producción

Simular la red con MSW

Mock Service Worker intercepta peticiones a nivel de red en vez de parchear fetch: tus componentes hacen sus llamadas reales y tú controlas la respuesta. Por qué es el estándar de 2026 para testear código que pide datos, y cómo escribir handlers.

En el capítulo anterior simulabas una dependencia inyectándola: el componente recibía cargarHeroes por props y tú le pasabas un doble. Limpio cuando puedes diseñarlo así. Pero muchas veces el componente hace su propio fetch por dentro, sin punto de inyección. Y aunque lo tuviera, a veces quieres probar la petición real: que va a la URL correcta, que maneja un 500. Para eso está MSW (Mock Service Worker).

Interceptar la red, no parchear fetch#

La idea de MSW es distinta a todo lo anterior. No sustituyes una función ni parcheas fetch: interceptas la petición a nivel de red. El componente hace su fetch("...") real, como en producción, y MSW lo atrapa y responde lo que tú digas. El componente no se entera ni cambia.

Las piezas:

  • Un handler describe cómo responder a una petición: http.get(url, resolver), donde el resolver devuelve una respuesta con HttpResponse.json(datos). Es tu API de mentira, declarada.
  • setupServer(...handlers) reúne los handlers en un servidor para los tests.
  • El ciclo de vida: server.listen() enciende la intercepción (en beforeAll), server.close() la apaga (en afterAll), y server.resetHandlers() (en afterEach) limpia entre tests.

Probar el error es la mitad del trabajo#

Fíjate en el segundo test del demo. Probar que el caso feliz funciona está bien, pero el código que pide datos falla de verdad: la API devuelve un 500, la red se cae, llega basura. Con MSW, simular eso es trivial: server.use sobrescribe un handler solo para ese test.

tsx
// Solo en este test: la API responde 500. El componente debe mostrar su estado de error.
server.use(
  http.get("https://api.equipo.test/miembros", () => new HttpResponse(null, { status: 500 })),
);

¿Y por qué importa tanto? Porque el camino de error es donde más bugs viven y donde menos se prueba (es incómodo provocar un fallo real). Un componente que pinta bien los datos pero se queda en blanco —o crashea— cuando la API falla es un bug que tu usuario verá. Con MSW lo pruebas en una línea, y afterEach(resetHandlers) se encarga de que ese 500 no se cuele en el siguiente test.

Por qué MSW es el estándar de 2026#

Podrías parchear fetch a mano (vi.spyOn(globalThis, "fetch")). Funciona, pero te acopla a la API exacta de fetch: si mañana el código usa axios, o cambian los argumentos, tu test se rompe aunque el comportamiento siga bien. MSW intercepta por debajo de la librería que uses: tus tests no se atan a cómo se hace la llamada, solo a la red.

Y hay un bonus que ninguna otra opción da: los mismos handlers sirven en los tests y en el navegador mientras desarrollas (MSW puede simular el backend en local con un service worker). Una descripción de tu API, dos usos: testear y desarrollar sin backend. Por eso es el estándar.

En este playground, setupServer corre sobre un interceptor de fetch adaptado al navegador, pero el código que escribes es el mismo que usarías en tu proyecto con Vitest. En el navegador real, MSW usa un service worker (de ahí su nombre); en los tests de Node, el setupServer que ves.

Pruébalo#

Abre el demo de arriba y cambia la respuesta del handler feliz: pon HttpResponse.json([]) (una lista vacía) y mira cómo el primer test pasa a rojo —ya no encuentra “Genji”—. Devuélvelo a su sitio. Has cambiado lo que “responde la API” sin tocar el componente: esa es la idea entera de MSW.

Comprueba lo que sabes#

Pregunta 1 de 5

En el capítulo anterior inyectabas la dependencia (cargarHeroes por props). ¿Qué hace MSW distinto?

Tu turno#

Prueba PanelHeroes con MSW: monta el servidor, gestiona su ciclo de vida y cubre el caso feliz y el de error. Cuando lo tengas (o si te atascas), despliega las soluciones y fíjate en el salto de un tier al siguiente: del caso feliz a la carga y el error, y de ahí al contrato completo.

Ejercicio · en esta página

Prueba PanelHeroes con MSW

Tienes PanelHeroes, un componente que hace su PROPIO fetch para cargar héroes (carga, datos, error, lista vacía). No tocas el componente: pruébalo con MSW. Monta un servidor con un handler, gestiona su ciclo de vida (listen/resetHandlers/close) y cubre el caso feliz y el de error con server.use.

Paso 1: El caso feliz interceptado

  • Montas un servidor MSW con un handler que responde a la petición del panel.
  • Gestionas su ciclo de vida con beforeAll(listen), afterEach(resetHandlers) y afterAll(close).
  • Compruebas con findByText que el panel pinta los héroes que "devolvió la API".
Ver soluciones
// Tier OK: el caso feliz. Un handler de MSW responde y el panel pinta los héroes.
import { describe, it, expect, beforeAll, afterEach, afterAll } from "vitest";
import { render, screen } from "@testing-library/react";
import { http, HttpResponse } from "msw";
import { setupServer } from "msw/node";
import { PanelHeroes } from "./PanelHeroes";

const server = setupServer(
  http.get("https://api.equipo.test/heroes", () =>
    HttpResponse.json([
      { nombre: "Genji", rol: "Daño" },
      { nombre: "Mercy", rol: "Apoyo" },
    ]),
  ),
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

describe("PanelHeroes", () => {
  it("muestra los héroes que vienen de la API", async () => {
    // El componente hace su fetch REAL; MSW lo intercepta y responde el handler de arriba.
    render(<PanelHeroes />);
    // findByText espera a que la respuesta llegue y el panel la pinte.
    expect(await screen.findByText("Genji")).toBeInTheDocument();
    expect(screen.getByText("Mercy")).toBeInTheDocument();
  });
});

Por qué este nivel

  • Monta un servidor MSW, gestiona su ciclo de vida (listen/resetHandlers/close) y prueba el caso feliz con findByText. El componente hace su fetch REAL: MSW intercepta la red, no parchea nada del componente.
  • Su límite: solo el caso feliz. Lo más valioso de testear la red —el estado de carga y, sobre todo, qué pasa cuando la API FALLA— se queda fuera.