← Todos los artículos

API y helpers

Simular respuestas de API interceptando fetch y XHR

El backend no está listo. O sí lo está, pero no puedes hacer que devuelva una lista vacía, un error 500 y un retardo de tres segundos a demanda. Este artículo muestra cómo interceptar fetch y XMLHttpRequest directamente en el navegador con una regla JS de JustZix, devolviendo JSON falso para que puedas construir y probar el frontend contra cualquier escenario que quieras.

¿Por qué interceptar en el navegador?

La respuesta habitual a «necesito datos falsos» es un servidor de mocks — MSW, json-server, un stub en tu entorno de desarrollo. Son excelentes, y para todo un equipo son la opción correcta. Pero a veces quieres algo más ligero:

Una regla JS de JustZix vive en el navegador, se aplica por patrón de URL y no necesita ningún cambio en el proyecto. Ese es su nicho: mocking rápido, quirúrgico y fácil de descartar.

Envolver window.fetch

El truco central es reemplazar window.fetch por tu propia función que decide, por cada petición, si simular o dejar pasar. Ejecuta esto en document_start para que esté instalado antes de que cualquier código de la app llame a fetch:

// Save the original so we can fall through to the real network
const realFetch = window.fetch.bind(window);

// Your mock table: match by URL substring -> response factory
const mocks = [
  {
    match: '/api/user/profile',
    response: () => jsonResponse({ id: 1, name: 'Ada Lovelace', plan: 'pro' })
  },
  {
    match: '/api/orders',
    response: () => jsonResponse([])   // the empty-list edge case
  }
];

window.fetch = async (input, init) => {
  const url = typeof input === 'string' ? input : input.url;
  const hit = mocks.find(m => url.includes(m.match));
  if (hit) {
    console.log('[mock] intercepted', url);
    return hit.response();
  }
  return realFetch(input, init);   // not mocked -> real request
};

Construir un Response sintético

Tu app espera un objeto Response real — llamará a .json(), comprobará .ok, leerá .status. El constructor Response te da todo eso gratis:

// Helper: a 200 OK JSON response
function jsonResponse(data, status = 200) {
  return new Response(JSON.stringify(data), {
    status,
    headers: { 'Content-Type': 'application/json' }
  });
}

Como es un Response genuino, el código que hace const r = await fetch(...); if (r.ok) return r.json(); funciona sin modificación. Ese es todo el sentido — la app no puede notar la diferencia.

Simular latencia

Las listas vacías y los caminos felices son fáciles. Los bugs viven en el camino lento: spinners que nunca se ocultan, condiciones de carrera, envíos duplicados. Añade un retardo antes de resolver:

// A small sleep helper
const sleep = (ms) => new Promise(r => setTimeout(r, ms));

// In the mock table, await it before returning
{
  match: '/api/search',
  response: async () => {
    await sleep(3000);            // 3 seconds of "loading..."
    return jsonResponse({ results: [] });
  }
}

Simular códigos de error

Ahora los caminos infelices. Un 500, un 404, un 401 que debería rebotarte al login — devuélvelos con el estado correcto para que tu manejo de errores realmente se ejecute:

// 500 Internal Server Error
{
  match: '/api/checkout',
  response: () => jsonResponse(
    { error: 'payment_gateway_timeout' },
    500
  )
}

// A network failure (fetch rejects, not resolves)
{
  match: '/api/flaky',
  response: () => Promise.reject(new TypeError('Failed to fetch'))
}

Fíjate en la diferencia: un 500 sigue resolviéndose con r.ok === false, mientras que una conexión caída rechaza. Prueba ambos — las apps con frecuencia gestionan uno y se caen con el otro.

Envolver XMLHttpRequest

Mucho código antiguo, y librerías como axios, siguen usando XMLHttpRequest por debajo. La interceptación de fetch no los toca. XHR es más engorroso de falsear, pero un envoltorio fino cubre el caso común:

const RealXHR = window.XMLHttpRequest;

window.XMLHttpRequest = function () {
  const xhr = new RealXHR();
  const realOpen = xhr.open;
  let mockedUrl = null;

  xhr.open = function (method, url, ...rest) {
    if (url.includes('/api/legacy')) mockedUrl = url;
    return realOpen.call(this, method, url, ...rest);
  };

  const realSend = xhr.send;
  xhr.send = function (...args) {
    if (!mockedUrl) return realSend.apply(this, args);

    // Fake a successful response without hitting the network
    setTimeout(() => {
      Object.defineProperty(xhr, 'readyState', { value: 4 });
      Object.defineProperty(xhr, 'status', { value: 200 });
      Object.defineProperty(xhr, 'responseText', {
        value: JSON.stringify({ legacy: true })
      });
      xhr.onreadystatechange && xhr.onreadystatechange();
      xhr.onload && xhr.onload();
    }, 0);
  };

  return xhr;
};

Esto es deliberadamente mínimo — cubre onload / onreadystatechange con texto JSON, que es lo que necesita el 90% del código XHR. Si una librería inspecciona getAllResponseHeaders() tendrás que stubear eso también. Llegado ese punto, sinceramente, recurre a un servidor de mocks real.

Cuándo usar un servidor de mocks real

La interceptación en el navegador es la herramienta correcta para trabajo rápido, acotado y desechable. Cambia a MSW o json-server cuando:

Regla práctica: si el mock debe commitearse, usa un servidor. Si debe desaparecer cuando desactivas una regla, usa JustZix.

Mira también

Deja de esperar al backend. Instala JustZix, coloca un envoltorio de fetch en una regla JS acotada y construye el frontend contra todos los escenarios hoy mismo.

Valora este artículo

Sin valoraciones — sé el primero.

Pruébalo tú mismo

Instala JustZix y pega cualquier snippet de este artículo. Dos minutos de cero a una regla funcionando en todos tus dispositivos.

Obtener JustZix

Funciones · Cómo funciona · Ejemplos · Casos de uso