API-Antworten durch Abfangen von fetch und XHR mocken
Das Backend ist nicht fertig. Oder es ist fertig, aber du kannst es nicht dazu bringen, auf Befehl eine leere Liste, einen 500er-Fehler und drei Sekunden Verzögerung zu liefern. Dieser Artikel zeigt, wie du fetch und XMLHttpRequest direkt im Browser mit einer JustZix-JS-Regel abfängst und gefälschtes JSON zurückgibst — so kannst du das Frontend gegen jedes beliebige Szenario bauen und testen.
Warum überhaupt im Browser abfangen
Die übliche Antwort auf „Ich brauche gefälschte Daten" ist ein Mock-Server — MSW, json-server, ein Stub in deiner Dev-Umgebung. Die sind großartig, und für ein ganzes Team sind sie die richtige Wahl. Aber manchmal willst du etwas Leichteres:
- Du debuggst die Produktion und kannst sie nicht auf ein gefälschtes Backend zeigen lassen.
- Du willst einen bestimmten Grenzfall reproduzieren, ohne das Projekt-Setup anzufassen.
- Du begutachtest die Seite von jemand anderem und hast überhaupt keinen Repo-Zugriff.
Eine JustZix-JS-Regel lebt im Browser, greift nach URL-Muster und braucht null Projektänderungen. Das ist ihre Nische: schnelles, chirurgisches, wegwerffreundliches Mocken.
window.fetch umhüllen
Der Kerntrick ist, window.fetch durch deine eigene Funktion zu ersetzen, die pro Anfrage entscheidet, ob gemockt oder durchgereicht wird. Lass das bei document_start laufen, damit es installiert ist, bevor irgendein App-Code fetch aufruft:
// 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
};
Eine synthetische Response bauen
Deine App erwartet ein echtes Response-Objekt — sie ruft .json() auf, prüft .ok, liest .status. Der Response-Konstruktor gibt dir das alles gratis:
// Helper: a 200 OK JSON response
function jsonResponse(data, status = 200) {
return new Response(JSON.stringify(data), {
status,
headers: { 'Content-Type': 'application/json' }
});
}
Weil es eine echte Response ist, funktioniert Code wie const r = await fetch(...); if (r.ok) return r.json(); ohne Änderung. Genau das ist der Sinn — die App merkt den Unterschied nicht.
Latenz simulieren
Leere Listen und Happy Paths sind einfach. Die Bugs leben im langsamen Pfad: Spinner, die nie verschwinden, Race Conditions, Doppel-Sends. Füge eine Verzögerung vor dem Auflösen ein:
// 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: [] });
}
}
Fehlercodes simulieren
Jetzt die unglücklichen Pfade. Ein 500er, ein 404er, ein 401er, der dich zum Login werfen sollte — gib sie mit dem richtigen Status zurück, damit deine Fehlerbehandlung wirklich läuft:
// 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'))
}
Beachte den Unterschied: Ein 500er löst trotzdem mit r.ok === false auf, während eine abgebrochene Verbindung verwirft. Teste beides — Apps behandeln häufig das eine und stürzen beim anderen ab.
XMLHttpRequest umhüllen
Reichlich älterer Code und Bibliotheken wie axios nutzen unter der Haube weiterhin XMLHttpRequest. Das Abfangen von fetch berührt sie nicht. XHR ist umständlicher zu fälschen, aber ein dünner Wrapper deckt den häufigen Fall ab:
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;
};
Das ist absichtlich minimal — es deckt onload / onreadystatechange mit JSON-Text ab, und das ist es, was 90 % des XHR-Codes brauchen. Inspiziert eine Bibliothek getAllResponseHeaders(), musst du das auch stubben. An dem Punkt greif, ehrlich gesagt, zu einem echten Mock-Server.
Wann stattdessen ein echter Mock-Server
Browser-Abfangen ist das richtige Werkzeug für schnelle, eng abgegrenzte Wegwerfarbeit. Wechsle zu MSW oder json-server, wenn:
- Das ganze Team dieselben Mocks braucht, versioniert im Repo.
- Du Body-Matching bei Anfragen, zustandsbehaftete Sequenzen oder Record-and-Replay brauchst.
- Deine Test-Suite (Playwright, Cypress) die Mocks in der CI ausführt.
Faustregel: Soll der Mock committet werden, nutze einen Server. Soll er verschwinden, sobald du eine Regel ausschaltest, nutze JustZix.
Siehe auch
- Eine A/B-Test-Variante erzwingen — mocke den Flag-Endpunkt, um einen Zweig zu pinnen.
- Einen Preis- und Bestandswächter bauen — beobachte das DOM statt des Netzwerks.
- Sofort einsetzbare Snippets im Beispiele-Bereich.
Hör auf, aufs Backend zu warten. Installiere JustZix, leg einen fetch-Wrapper in eine eng abgegrenzte JS-Regel und baue das Frontend noch heute gegen jedes Szenario.
Bewerte diesen Beitrag
Noch keine Bewertungen — sei der Erste.