Fetch und Netzwerk in Tests mocken mit MSW
AI generated
JS
() =>
JavaScript · Testing Advanced · MSW · API Mocking
Fetch und Netzwerk in Tests mocken
mit Mock Service Worker auf HTTP-Ebene statt Funktionsattrappen

Wer in Unit-Tests einfach globalThis.fetch überschreibt, baut fragile Mocks, die bei jeder Code-Änderung neu angepasst werden müssen. Mock Service Worker fängt Netzwerk-Requests stattdessen auf HTTP-Ebene ab, testet den echten Fetch-Aufruf und lässt sich für Erfolgs- wie Fehlerfälle präzise konfigurieren, ohne den Anwendungscode selbst zu verändern.

17 Min. Lesezeit MSW · Vitest · Fetch API · Request Handler Node.js 20+ · Browser & Server

1. Warum Netzwerk-Mocking wichtiger ist als es klingt

Tests, die echte HTTP-Requests gegen ein Live-Backend absetzen, sind aus mehreren Gründen problematisch. Sie sind langsam, weil jeder Test auf eine echte Netzwerkantwort wartet. Sie sind unzuverlässig, weil ein kurzzeitiger Ausfall des Backends oder ein Rate-Limit den gesamten Testlauf grundlos scheitern lässt. Und sie sind schwer zu kontrollieren, weil Randfälle wie ein 500er-Fehler oder ein Timeout im echten System kaum gezielt reproduzierbar sind. Genau deshalb ist Fetch und Netzwerk in Tests mocken eine Grundvoraussetzung für eine schnelle, deterministische Testsuite.

Der naive Ansatz, globalThis.fetch direkt mit einer Testdouble-Funktion zu überschreiben, funktioniert für einfache Fälle, wird aber schnell unübersichtlich, sobald eine Komponente mehrere unterschiedliche Endpunkte aufruft. Jeder Testfall müsste dann selbst unterscheiden, welche URL welche Antwort zurückgeben soll, oft mit fragilen if-Ketten innerhalb der Mock-Implementierung. Fetch und Netzwerk in Tests mocken auf einer höheren Abstraktionsebene, wie es Mock Service Worker anbietet, löst dieses Problem grundlegend anders.

Mock Service Worker, kurz MSW, fängt Requests nicht auf Funktionsebene ab, sondern auf Netzwerkebene, direkt beim Interceptor für fetch, XMLHttpRequest oder in Node.js über die native http-Modul-Interception. Das bedeutet: Der Anwendungscode ruft ganz normal fetch('/api/products') auf, ohne zu wissen, dass die Antwort von einem Mock statt einem echten Server kommt. Dieser Unterschied macht Tests realistischer, weil exakt derselbe Code-Pfad getestet wird, der auch in Produktion läuft.

2. Drei Ansätze im Überblick: Stub, Interceptor, Service Worker

Der einfachste Ansatz ist das direkte Stubben der Fetch-Funktion, etwa mit vi.fn() in Vitest. Das funktioniert für triviale Fälle, koppelt den Test aber eng an die konkrete Implementierung, wie fetch aufgerufen wird, statt an das, was tatsächlich über das Netzwerk läuft. Ändert sich intern von fetch zu axios, muss jeder betroffene Test angepasst werden, obwohl sich am eigentlichen Verhalten nichts geändert hat.

Ein zweiter Ansatz ist die Interception auf HTTP-Client-Ebene, wie es Bibliotheken wie Nock für Node.js anbieten. Nock funktioniert gut für reine Node.js-Backend-Tests, unterstützt aber Browser-Umgebungen nicht nativ. Der dritte und heute bevorzugte Ansatz ist Fetch und Netzwerk in Tests mocken mit MSW, das dieselbe Handler-Definition sowohl im Browser als auch in Node.js verwendet und damit für Unit-Tests, Komponententests und sogar für die Entwicklung im Browser über einen echten Service Worker gleichermaßen funktioniert.

3. MSW installieren und den ersten Handler schreiben

Die Installation erfolgt über npm, und für Node.js-basierte Testumgebungen wie Vitest wird der Server-Modus von MSW verwendet, nicht der Service-Worker-Modus für Browser. Der zentrale Baustein ist http.get() beziehungsweise die entsprechenden Methoden für andere HTTP-Verben, mit denen ein Handler definiert wird, der auf eine bestimmte URL reagiert.


// npm install --save-dev msw vitest
// mocks/handlers.js
import { http, HttpResponse } from 'msw';

export const handlers = [
  http.get('/api/products/:id', ({ params }) => {
    return HttpResponse.json({
      id: params.id,
      name: 'Wireless Keyboard',
      price: 49.99,
      inStock: true,
    });
  }),

  http.get('/api/products', () => {
    return HttpResponse.json([
      { id: '1', name: 'Wireless Keyboard', price: 49.99 },
      { id: '2', name: 'USB-C Hub', price: 29.5 },
    ]);
  }),
];

// mocks/server.js — Node.js server for test environments
import { setupServer } from 'msw/node';
import { handlers } from './handlers.js';

export const server = setupServer(...handlers);

Wichtig beim Einstieg in Fetch und Netzwerk in Tests mocken mit MSW ist die Trennung zwischen Handler-Definition und Server-Instanz. Handler beschreiben, wie auf einen Request reagiert wird, während der Server über setupServer() diese Handler aktiv registriert und den echten Netzwerkverkehr während der Testlaufzeit abfängt. Diese Trennung erlaubt es, Handler pro Testdatei oder sogar pro einzelnem Test zu überschreiben, ohne die globale Konfiguration zu verändern.

4. Handler für unterschiedliche Endpunkte definieren

In realen Anwendungen ruft eine einzelne Komponente oft mehrere Endpunkte auf, zum Beispiel Produktdaten, Lagerbestand und Nutzerdaten. Fetch und Netzwerk in Tests mocken erfordert deshalb eine Struktur, in der Handler klar nach Domäne organisiert sind, statt alle Endpunkte in einer einzigen, unübersichtlichen Datei zu sammeln. MSW unterstützt Pfad-Parameter, Query-Parameter und verschiedene HTTP-Methoden nativ, was komplexe REST-APIs realistisch abbildet.


import { http, HttpResponse } from 'msw';

export const cartHandlers = [
  // Path parameter matching
  http.get('/api/cart/:cartId', ({ params }) => {
    return HttpResponse.json({ cartId: params.cartId, items: [], total: 0 });
  }),

  // Reading the request body for POST requests
  http.post('/api/cart/:cartId/items', async ({ request, params }) => {
    const body = await request.json();
    return HttpResponse.json(
      { cartId: params.cartId, added: body.sku, quantity: body.quantity },
      { status: 201 }
    );
  }),

  // Query parameter matching
  http.get('/api/search', ({ request }) => {
    const url = new URL(request.url);
    const query = url.searchParams.get('q');
    if (!query) {
      return HttpResponse.json({ error: 'Missing query parameter' }, { status: 400 });
    }
    return HttpResponse.json({ query, results: [] });
  }),
];

5. Fehlerfälle simulieren: Timeouts, 500er, Rate-Limits

Der eigentliche Mehrwert beim Fetch und Netzwerk in Tests mocken zeigt sich nicht bei Erfolgsfällen, sondern bei Fehlerszenarien, die in einem echten Backend kaum gezielt provozierbar sind. Wie reagiert die Anwendung, wenn die API mit Status 500 antwortet? Zeigt die UI eine sinnvolle Fehlermeldung, statt einfach hängen zu bleiben? Wie verhält sich die Retry-Logik bei einem 429 Too Many Requests? Solche Fragen lassen sich mit MSW präzise und wiederholbar beantworten, weil jeder Handler exakt festlegt, welche Antwort zurückkommt.

Für Netzwerkfehler wie einen abgebrochenen Request oder einen Timeout bietet MSW die Funktion HttpResponse.error(), die einen echten Netzwerkfehler simuliert, wie ihn der Browser bei einer fehlgeschlagenen Verbindung wirft. Das unterscheidet sich fundamental von einer Fehlerantwort mit HTTP-Statuscode, weil der Fetch-Aufruf selbst mit einer Exception fehlschlägt, statt eine gültige Response mit Fehlerstatus zurückzugeben. Beide Fälle müssen von robustem Anwendungscode unterschiedlich behandelt werden, und beide lassen sich beim Netzwerk-Mocking gezielt reproduzieren.


import { http, HttpResponse, delay } from 'msw';
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { server } from '../mocks/server.js';

describe('error handling with mocked network failures', () => {
  it('shows a friendly message on 500 Internal Server Error', async () => {
    server.use(
      http.get('/api/products', () => {
        return HttpResponse.json({ error: 'Internal Server Error' }, { status: 500 });
      })
    );

    const result = await fetchProductsSafely();
    expect(result.error).toBe('Something went wrong. Please try again.');
  });

  it('retries on 429 Too Many Requests', async () => {
    let attempts = 0;
    server.use(
      http.get('/api/products', () => {
        attempts += 1;
        if (attempts < 3) {
          return HttpResponse.json({ error: 'Rate limited' }, { status: 429 });
        }
        return HttpResponse.json([{ id: '1', name: 'Wireless Keyboard' }]);
      })
    );

    const result = await fetchProductsWithRetry();
    expect(attempts).toBe(3);
    expect(result).toHaveLength(1);
  });

  it('handles a genuine network failure, not just an HTTP error status', async () => {
    server.use(http.get('/api/products', () => HttpResponse.error()));
    await expect(fetchProductsSafely()).resolves.toEqual({
      error: 'Network unavailable. Check your connection.',
    });
  });

  it('simulates slow responses to test loading states', async () => {
    server.use(
      http.get('/api/products', async () => {
        await delay(3000);
        return HttpResponse.json([]);
      })
    );
    // Assert a loading spinner is shown while the request is pending
  });
});

6. Dynamisches Verhalten: Request-Body und Query prüfen

Ein oft übersehener Vorteil beim Fetch und Netzwerk in Tests mocken mit MSW ist die Möglichkeit, den ausgehenden Request selbst zu inspizieren, nicht nur die Antwort zu kontrollieren. Innerhalb eines Handlers steht das vollständige request-Objekt zur Verfügung, inklusive Headers, Body und URL. Das erlaubt Assertions darüber, ob die Anwendung tatsächlich die korrekten Daten sendet, etwa den richtigen Authorization-Header oder das korrekte JSON-Payload beim Anlegen einer Bestellung.

Diese Fähigkeit verwandelt Netzwerk-Mocking von einer reinen Antwort-Simulation in ein vollwertiges Werkzeug für Contract-ähnliche Prüfungen: Der Test bestätigt nicht nur, dass die Komponente mit einer bestimmten Antwort korrekt umgeht, sondern auch, dass sie überhaupt die richtige Anfrage stellt. Kombiniert mit Vitest-Spies auf den Handler selbst lässt sich zusätzlich prüfen, wie oft ein Endpunkt aufgerufen wurde, relevant für das Erkennen unnötiger doppelter Requests.

7. Integration in Vitest-Suiten und Setup-Dateien

Damit Fetch und Netzwerk in Tests mocken in der gesamten Testsuite konsistent funktioniert, wird der MSW-Server in einer globalen Setup-Datei gestartet, gestoppt und zwischen Tests zurückgesetzt. Das Zurücksetzen mit server.resetHandlers() nach jedem Test ist entscheidend, weil sonst ein in einem Test überschriebener Handler versehentlich in nachfolgenden Tests aktiv bleibt und zu schwer nachvollziehbaren Fehlschlägen führt.


// vitest.setup.js
import { beforeAll, afterEach, afterAll } from 'vitest';
import { server } from './mocks/server.js';

beforeAll(() => {
  // Fail loudly on any request without a matching handler
  server.listen({ onUnhandledRequest: 'error' });
});

afterEach(() => {
  server.resetHandlers(); // discard per-test handler overrides
});

afterAll(() => {
  server.close();
});

// vitest.config.js
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    setupFiles: ['./vitest.setup.js'],
    environment: 'jsdom',
  },
});

Die Option onUnhandledRequest: 'error' ist beim Fetch und Netzwerk in Tests mocken besonders wertvoll: Sie lässt einen Test sofort fehlschlagen, wenn ein Request an eine URL geht, für die kein Handler existiert, statt den Request stillschweigend an das echte Netzwerk durchzulassen. Ohne diese Absicherung könnten Tests unbemerkt echte HTTP-Aufrufe absetzen, was die gesamte Idee des Netzwerk-Mockings unterläuft.

8. Grenzen: Wann Netzwerk-Mocking nicht ausreicht

Netzwerk-Mocking mit MSW prüft, ob die Anwendung korrekt mit einer angenommenen API-Antwort umgeht, nicht ob diese Annahme über die echte API-Struktur zutrifft. Ändert das Backend ein Feld im JSON-Response, ohne dass die Handler-Definition aktualisiert wird, bleiben alle Tests grün, während die Anwendung in Produktion bricht. Dieses Risiko lässt sich nur durch ergänzende Contract-Tests oder Integrationstests gegen eine echte oder gestagte API absichern, nicht durch Netzwerk-Mocking allein.

Ein zweiter Grenzfall betrifft echtes Netzwerkverhalten wie CORS-Beschränkungen, TLS-Zertifikatsprobleme oder tatsächliche Latenz unter Last. Diese Aspekte lassen sich mit MSW gar nicht oder nur unvollständig simulieren, weil MSW auf einer Abstraktionsebene oberhalb des tatsächlichen Netzwerkstacks arbeitet. Für solche Fälle bleiben dedizierte End-to-End-Tests gegen eine echte Staging-Umgebung notwendig, Netzwerk-Mocking ersetzt sie nicht, sondern ergänzt sie um schnelle, deterministische Unit- und Komponententests.

9. Netzwerk-Mocking-Ansätze im Vergleich

Die folgende Tabelle vergleicht die gängigsten Ansätze zum Mocken von Fetch und Netzwerkverkehr in JavaScript-Tests.

Ansatz Abstraktionsebene Browser & Node.js Geeignet für
vi.fn() Fetch-Stub Funktionsebene, eng gekoppelt Nur wo fetch direkt aufgerufen wird Sehr einfache, isolierte Einzelfälle
Nock HTTP-Client-Ebene Nur Node.js Reine Backend-/Node.js-Tests
MSW Netzwerkebene, realistisch Beide, identische Handler Unit-Tests, Komponententests, Storybook
Echtes Test-Backend Vollständig realistisch Beide, aber langsam E2E- und Contract-Tests

Für die meisten JavaScript-Projekte ist MSW der pragmatischste Kompromiss zwischen Realismus und Testgeschwindigkeit: Es testet echten Fetch-Code, unterstützt Browser und Node.js mit derselben Handler-Definition und lässt sich sowohl in isolierten Unit-Tests als auch in Storybook für die Entwicklung einsetzen.

Mironsoft

Belastbare JavaScript-Testsuiten für Magento- und Hyvä-Frontends

Schnelle, deterministische Tests ohne echte API-Aufrufe?

Wir führen Mock Service Worker in eure bestehenden Vitest- oder Jest-Suiten ein, modellieren Erfolgs- und Fehlerfälle sauber und entkoppeln eure Frontend-Tests vollständig von echten Backend-Aufrufen.

MSW-Einführung

Handler-Struktur, Server-Setup und CI-Integration von Grund auf aufsetzen

Fehlerfall-Coverage

Timeouts, 500er und Rate-Limits gezielt simulieren und absichern

Bestehende Tests migrieren

Fragile Fetch-Stubs schrittweise durch robuste MSW-Handler ersetzen

10. Zusammenfassung

Fetch und Netzwerk in Tests mocken bedeutet, Netzwerk-Requests auf einer realistischen Ebene abzufangen, statt fragile Funktionsattrappen für fetch zu bauen. Mock Service Worker erreicht das über Handler, die auf HTTP-Methode und URL reagieren, identisch nutzbar im Browser und in Node.js. Fehlerfälle wie 500er-Antworten, echte Netzwerkfehler über HttpResponse.error() und verzögerte Antworten lassen sich präzise und reproduzierbar simulieren, was in einem echten Backend kaum möglich wäre.

Die konsequente Integration über server.resetHandlers() zwischen Tests und onUnhandledRequest: 'error' verhindert, dass unbemerkte echte Netzwerkaufrufe die Testsuite verfälschen. Netzwerk-Mocking ersetzt keine Contract-Tests oder echten Integrationstests gegen eine Staging-Umgebung, ergänzt sie aber um schnelle, deterministische und gezielt provozierbare Fehlerszenarien, die für robusten Anwendungscode unverzichtbar sind.

Fetch und Netzwerk in Tests mocken — Das Wichtigste auf einen Blick

Abstraktionsebene

MSW fängt Requests auf Netzwerkebene ab, nicht auf Funktionsebene, testet damit echten Fetch-Code.

Handler-Struktur

http.get()/http.post() mit Path- und Query-Parameter-Matching, klar nach Domäne organisiert.

Fehlerfälle

HttpResponse.error() für echte Netzwerkfehler, Status-Codes für HTTP-Fehlerantworten, delay() für Timeouts.

CI-Sicherheit

resetHandlers() nach jedem Test, onUnhandledRequest: 'error' gegen unbemerkte echte Requests.

11. FAQ: Fetch und Netzwerk in Tests mocken

1Warum nicht einfach fetch überschreiben?
Zu eng an die Implementierung gekoppelt. MSW fängt auf Netzwerkebene ab und testet den echten Fetch-Aufruf.
2MSW vs. Nock?
Nock nur Node.js, MSW Browser und Node.js mit derselben Handler-Definition.
3Echten Netzwerkfehler simulieren?
Mit HttpResponse.error(), lässt fetch selbst mit Exception fehlschlagen statt Fehlerstatus zurückzugeben.
4onUnhandledRequest: error?
Lässt Tests bei fehlendem Handler sofort fehlschlagen statt echten Netzwerkverkehr stillschweigend zuzulassen.
5Warum resetHandlers() nötig?
Verhindert, dass überschriebene Handler aus einem Test in nachfolgende Tests durchsickern.
6Request-Body prüfbar?
Ja, über await request.json() innerhalb des Handlers, inklusive Headers und URL.
7Ersetzt es Contract-Tests?
Nein, prüft nur den Umgang mit angenommener Antwort, nicht ob diese der echten API entspricht.
8Funktioniert es mit Axios?
Ja, weil MSW auf Netzwerkebene abfängt, unabhängig vom verwendeten HTTP-Client.
9Ladezustände testen?
Mit der delay()-Funktion aus MSW, um Loading-Spinner gezielt sichtbar zu machen.
10MSW im Browser einsetzbar?
Ja, über den Service-Worker-Modus, ideal für Entwicklung ohne echtes Backend.