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:
- Estás depurando producción y no puedes apuntarla a un backend falso.
- Quieres reproducir un caso límite concreto sin tocar la configuración del proyecto.
- Estás revisando el sitio de otra persona y no tienes acceso al repositorio.
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:
- Todo el equipo necesita los mismos mocks, versionados en el repositorio.
- Necesitas coincidencia por cuerpo de la petición, secuencias con estado, o grabar y reproducir.
- Tu suite de tests (Playwright, Cypress) ejecuta los mocks en CI.
Regla práctica: si el mock debe commitearse, usa un servidor. Si debe desaparecer cuando desactivas una regla, usa JustZix.
Mira también
- Forzar una variante de test A/B — simula el endpoint del flag para fijar una rama.
- Crear un vigilante de precio y stock — observa el DOM en lugar de la red.
- Fragmentos listos para pegar en la sección de Ejemplos.
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.