Mock Service Worker (MSW) für API-Mocking in Vue-Tests
AI generated
{ }
Vue · Testing · MSW
Mock Service Worker für Vue
API-Mocking auf Netzwerk-Ebene statt auf Funktions-Ebene

Mock Service Worker fängt HTTP-Anfragen an der Netzwerkschnittstelle ab, bevor sie überhaupt versendet werden, statt einzelne JavaScript-Funktionen wie Axios-Aufrufe zu ersetzen. Das macht Vue-Tests realistischer, weil der komplette Anfrage-Antwort-Zyklus durchlaufen wird, inklusive Statuscodes, Headern und Fehlerpfaden, ohne dass der Testcode wissen muss, welche HTTP-Bibliothek eine Komponente intern verwendet.

14 Min. Lesezeit Mock Service Worker Vitest

1. Mocking auf Netzwerk-Ebene statt Funktions-Ebene

Klassisches Mocking mit vi.mock('axios') oder vi.mock('./api') ersetzt ein komplettes Modul durch eine Fake-Implementierung, die der Testcode selbst definiert. Das funktioniert, koppelt den Test aber eng an die konkrete Implementierung: Wechselt eine Komponente von Axios zu Fetch, oder ändert sich der interne Aufbau des API-Moduls, müssen sämtliche Mocks angepasst werden, obwohl sich am eigentlichen Verhalten der Anwendung nichts geändert hat.

Mock Service Worker (MSW) setzt an einer anderen Stelle an: Es fängt Anfragen direkt auf Netzwerkebene ab, entweder über einen echten Service Worker im Browser oder über eine Node-Interception in Testumgebungen, und beantwortet sie mit definierten Handlern. Die getestete Komponente merkt davon nichts, sie sendet eine ganz normale HTTP-Anfrage über Fetch oder Axios und bekommt eine Antwort, die aussieht wie eine echte Server-Antwort. Der Test bleibt dadurch unabhängig davon, welche HTTP-Bibliothek intern verwendet wird.


// src/mocks/handlers.ts
import { http, HttpResponse } from 'msw'

export const handlers = [
  http.get('/api/products/:id', ({ params }) => {
    return HttpResponse.json({
      id: params.id,
      name: 'Teekanne',
      price: 29.9,
    })
  }),

  http.post('/api/cart/items', async ({ request }) => {
    const body = await request.json()
    return HttpResponse.json({ id: 'cart-item-1', ...body }, { status: 201 })
  }),

  http.get('/api/products/:id/reviews', () => {
    return HttpResponse.json([], { status: 200 })
  }),
]

2. Setup für Vitest

Die Einrichtung von MSW für Vitest erfolgt über setupServer aus msw/node, das die Handler in einem Test-Setup-File registriert. Vitest lädt dieses Setup-File automatisch vor jedem Testlauf, sodass sämtliche Testdateien standardmäßig auf die gleichen, zentral definierten Handler zugreifen, ohne dass jede Datei ihr eigenes Mocking aufbauen muss. Der Server wird vor allen Tests gestartet, nach jedem Test zurückgesetzt und nach allen Tests wieder geschlossen.

Wichtig ist, den Server-Reset nach jedem Test nicht zu vergessen, weil sonst Handler-Overrides aus einem Test in den nächsten durchsickern können. Ein sauberer Reset stellt sicher, dass jeder Test mit dem gleichen, vorhersagbaren Ausgangszustand startet, unabhängig davon, in welcher Reihenfolge die Testdateien ausgeführt werden. Gerade bei parallel laufenden Testsuiten ist das ein Detail, das leicht übersehen wird, aber schwer zu debuggende, sporadisch fehlschlagende Tests verursacht, wenn es fehlt.

3. Request-Handler für typische REST-Endpoints

Handler in MSW werden pro HTTP-Methode und Pfad definiert, wobei Pfadparameter wie :id automatisch geparst und über das params-Objekt im Handler zugänglich gemacht werden. Für typische CRUD-Endpoints reicht meist ein Handler pro Methode und Ressource: GET für Einzelabruf und Listen, POST für Erstellung, PUT oder PATCH für Aktualisierung, DELETE für Löschung. Query-Parameter lassen sich über das request-Objekt und dessen url-Eigenschaft auslesen, etwa für Filter- oder Paginierungslogik.

Für Fehlerpfade lässt sich derselbe Endpoint in einem einzelnen Test gezielt überschreiben, ohne den globalen Handler dauerhaft zu ändern. Über server.use(...) innerhalb eines konkreten Tests wird für diesen einen Testfall eine Fehlerantwort simuliert, etwa ein 500er-Statuscode oder eine Netzwerk-Fehlerantwort, während alle anderen Tests weiterhin den erfolgreichen Standard-Handler verwenden. Das macht das Testen von Error-Boundaries und Fehlermeldungen in der UI deutlich einfacher als mit manuell konstruierten Mock-Rückgabewerten.

4. Der Vorteil gegenüber vi.mock() bei Axios/Fetch

Mit vi.mock('axios') muss der Testcode für jeden getesteten Aufruf genau wissen, welche Axios-Methode intern verwendet wird, ob axios.get, eine Axios-Instanz mit axios.create() oder ein Interceptor beteiligt ist. Ändert sich diese interne Struktur, etwa weil ein Team von direkten Axios-Aufrufen auf eine zentrale API-Client-Klasse umstellt, müssen sämtliche vi.mock-Aufrufe in allen betroffenen Tests angepasst werden, selbst wenn sich am HTTP-Vertrag der Anwendung nichts geändert hat.

MSW abstrahiert genau diese Implementierungsdetails weg, weil es an der Netzwerkgrenze ansetzt statt an der JavaScript-API-Grenze. Ein Wechsel von Axios zu Fetch, oder das Einführen eines eigenen API-Wrapper-Moduls, erfordert keine Änderung an den MSW-Handlern, solange die tatsächlichen HTTP-Anfragen unverändert bleiben. Das macht Tests robuster gegenüber internen Refactorings und verringert die Kopplung zwischen Testcode und Implementierungsdetails erheblich, was gerade bei größeren Vue-Projekten mit vielen API-Aufrufen einen spürbaren Wartungsvorteil bringt.

5. Ein Komponententest mit MSW im Zusammenspiel

In einem konkreten Komponententest wird MSW meist im Hintergrund über das globale Setup aktiv, ohne dass die einzelne Testdatei etwas davon direkt importiert, außer wenn ein Handler für genau diesen Test überschrieben werden soll. Die getestete Komponente lädt Daten wie gewohnt über ihre normale API-Schicht, MSW beantwortet die Anfrage im Hintergrund, und der Test prüft anschließend, ob die Komponente die empfangenen Daten korrekt anzeigt.

Dieses Vorgehen erlaubt es, echte Async-Flows realistisch zu testen, inklusive Ladezuständen zwischen Anfrage und Antwort, weil MSW die Antwort erst nach einem echten (wenn auch schnellen) Promise-Zyklus liefert. Tests, die auf await flushPromises() oder ähnliche Hilfsfunktionen zurückgreifen, um auf die asynchrone Antwort zu warten, verhalten sich dadurch näher am tatsächlichen Laufzeitverhalten der Anwendung als bei synchron aufgelösten, manuell konstruierten Mocks.

6. Browser-Setup vs. Node-Setup

MSW unterscheidet zwischen zwei Betriebsmodi: Im Browser, etwa für Storybook oder eine lokale Entwicklungsumgebung, registriert sich ein echter Service Worker über setupWorker und fängt Anfragen ab, bevor sie den Browser verlassen. In Node-basierten Testläufern wie Vitest kommt stattdessen setupServer aus msw/node zum Einsatz, das auf einer Interception-Bibliothek basiert, weil Node keinen echten Service Worker kennt.

Der entscheidende Vorteil ist, dass dieselben Handler-Definitionen in beiden Umgebungen wiederverwendet werden können. Ein Handler-File, das für Storybook geschrieben wurde, um Komponenten isoliert mit realistischen API-Antworten zu entwickeln, lässt sich unverändert auch in Vitest-Tests einbinden. Das spart doppelte Pflege von Mock-Daten und stellt sicher, dass Entwicklungsumgebung und Testumgebung dieselben Annahmen über die API-Form teilen.

7. Fehlerpfade und Netzwerkfehler gezielt simulieren

Neben normalen Erfolgsantworten lässt sich mit MSW auch das Verhalten bei echten Netzwerkfehlern testen, etwa über HttpResponse.error(), was einen kompletten Verbindungsabbruch simuliert, im Unterschied zu einer Antwort mit Fehlerstatuscode wie 404 oder 500. Diese Unterscheidung ist wichtig, weil viele Frontend-Fehlerbehandlungen unterschiedlich auf einen abgelehnten Fetch-Promise reagieren als auf eine technisch erfolgreiche Antwort mit Fehlerstatus.

Auch Verzögerungen lassen sich gezielt simulieren, etwa über delay() aus dem MSW-Paket, um Ladezustände und Race-Conditions zwischen mehreren gleichzeitigen Anfragen realistisch zu testen. Ein Test, der prüft, ob ein Ladeindikator korrekt verschwindet, sobald die letzte von mehreren parallelen Anfragen beantwortet wurde, profitiert deutlich von einer kontrollierten, aber realistischen Verzögerung, statt sich auf zufällige Timing-Eigenschaften synchroner Mocks zu verlassen.

8. Wiederverwendbare Handler-Struktur pflegen

In größeren Projekten lohnt es sich, Handler nach Domäne zu organisieren, etwa eine Datei für Produkt-Endpoints, eine für Warenkorb-Endpoints und eine für Nutzerkonto-Endpoints, statt alle Handler in einer einzigen, schnell unübersichtlichen Datei zu sammeln. Diese Handler-Dateien lassen sich dann selektiv importieren, sodass ein Test nur die Handler lädt, die für den jeweiligen Testbereich tatsächlich relevant sind, was die Testdatei lesbarer macht und ungewollte Interaktionen zwischen unabhängigen Testbereichen reduziert.

Ein weiterer Vorteil dieser Struktur zeigt sich beim Onboarding neuer Teammitglieder: Wer die Handler-Dateien liest, bekommt gleichzeitig eine dokumentierte Übersicht über die tatsächlich verwendeten API-Endpoints und deren erwartete Antwortform, was oft aktueller ist als separate API-Dokumentation, weil die Handler direkt mit den Tests gepflegt werden müssen, um diese lauffähig zu halten.

9. Grenzen von MSW und was es nicht ersetzt

So realistisch MSW den Anfrage-Antwort-Zyklus auch simuliert, es prüft an keiner Stelle, ob die Handler-Definitionen tatsächlich mit der echten Backend-API übereinstimmen. Ein Handler kann problemlos ein Feld liefern, das die reale API längst umbenannt hat, ohne dass ein Test dies bemerkt, weil MSW lediglich das zurückgibt, was im Handler definiert wurde, unabhängig davon, ob dieses Format noch der Realität entspricht. Diese Lücke zwischen Mock und echter API wird als Contract-Drift bezeichnet und ist ein bekanntes Risiko bei jeder Form von Mocking, nicht nur bei MSW.

Um dieses Risiko zu begrenzen, ergänzen viele Teams ihre MSW-Handler um regelmäßige Contract-Tests gegen die echte API, etwa in einer separaten Testsuite, die tatsächlich gegen eine Staging-Umgebung läuft und stichprobenartig prüft, ob die angenommene Antwortform noch stimmt. MSW ersetzt also keine Integrationstests gegen ein echtes Backend, sondern ergänzt sie um eine schnelle, isolierte Ebene für die Frontend-Logik, während die Verantwortung für die Korrektheit der Handler-Definitionen selbst weiterhin bei den Entwicklern liegt, die sie pflegen.

Aspekt vi.mock() (Funktionsebene) MSW (Netzwerkebene)
Kopplung an konkrete HTTP-Bibliothek unabhängig von Axios/Fetch
Realismus manuell konstruierte Rückgabewerte echter Anfrage-Antwort-Zyklus
Wiederverwendung pro Testdatei neu definiert zentrale Handler-Dateien
Fehlerpfade muss manuell simuliert werden HttpResponse.error() und Statuscodes
Storybook-Kompatibilität nicht direkt nutzbar gleiche Handler wiederverwendbar

Mironsoft

Vue-Architektur, Composition API und Nuxt-Performance

Vue-Anwendungen, die mit jedem Feature nicht komplizierter werden?

Wir prüfen bestehende Vue- und Nuxt-Projekte auf unstrukturierte Composables, ungenutzte Reaktivität und aufgeblähte Bundles und bauen daraus eine Architektur, die neue Features aufnimmt, ohne die Codebasis unübersichtlicher zu machen.

Architektur-Review

Composables, State-Management und Komponentenstruktur auf Wartbarkeit prüfen.

Performance-Audit

Reaktivitäts-Overhead, Bundle-Größe und Nuxt-Rendering-Strategie systematisch optimieren.

Nuxt-Integration

SSR/SSG-Setup und API-Anbindung robust und typsicher aufbauen.

10. Zusammenfassung

MSW für Vue-Tests: Das Wichtigste auf einen Blick

Ansatzpunkt

MSW mockt auf Netzwerkebene, nicht auf Funktionsebene

Setup

setupServer aus msw/node, Reset nach jedem Test nicht vergessen

Handler

pro HTTP-Methode und Pfad definiert, in Tests gezielt überschreibbar

Vorteil

unabhängig davon, ob Axios, Fetch oder ein eigener API-Wrapper verwendet wird

11. FAQ: MSW für Vue-Tests: Das Wichtigste auf einen Blick

1Was ist der Kernunterschied zwischen MSW und vi.mock()?
vi.mock() ersetzt ein JavaScript-Modul durch eine Fake-Implementierung, MSW fängt die tatsächliche HTTP-Anfrage auf Netzwerkebene ab. Dadurch bleibt der Testcode unabhängig davon, welche HTTP-Bibliothek intern verwendet wird.
2Muss ich MSW nach jedem Test zurücksetzen?
Ja, über server.resetHandlers() in einem afterEach-Hook. Ohne Reset können Handler-Overrides aus einem Test in nachfolgende Tests durchsickern und sporadische Fehlschläge verursachen.
3Kann ich dieselben Handler in Storybook und Vitest verwenden?
Ja, das ist einer der Hauptvorteile. Die gleichen Handler-Definitionen lassen sich mit setupWorker im Browser und mit setupServer in Node-Testläufern verwenden.
4Wie simuliere ich einen Netzwerkfehler mit MSW?
Über HttpResponse.error() innerhalb eines Handlers. Das simuliert einen kompletten Verbindungsabbruch, im Unterschied zu einer Antwort mit Fehlerstatuscode wie 500.
5Funktioniert MSW auch mit Fetch statt Axios?
Ja, MSW ist unabhängig von der verwendeten HTTP-Bibliothek, weil es auf Netzwerkebene ansetzt. Sowohl Fetch- als auch Axios-Aufrufe werden gleichermaßen abgefangen.
6Wie überschreibe ich einen Handler nur für einen einzelnen Test?
Über server.use(...) innerhalb des jeweiligen Tests. Der Override gilt nur bis zum nächsten Reset und beeinflusst keine anderen Tests.
7Brauche ich für Vitest einen echten Service Worker?
Nein, in Node-Umgebungen wie Vitest kommt setupServer aus msw/node zum Einsatz, das auf einer Interception-Bibliothek basiert statt auf einem echten Browser-Service-Worker.
8Kann ich mit MSW auch Ladezustände testen?
Ja, über die delay()-Funktion aus dem MSW-Paket lässt sich eine kontrollierte Verzögerung in die Antwort einbauen, um Ladeindikatoren und Race-Conditions realistisch zu testen.
9Wie organisiere ich Handler in einem größeren Projekt?
Am besten nach Domäne aufgeteilt in mehrere Dateien, etwa Produkt-, Warenkorb- und Nutzerkonto-Handler, statt alles in einer einzigen Datei zu sammeln.
10Lohnt sich MSW auch bei kleinen Projekten mit wenigen API-Aufrufen?
Bei sehr wenigen, stabilen Endpoints kann vi.mock() ausreichen. Sobald mehrere Komponenten dieselben Endpoints nutzen oder die HTTP-Bibliothek wechseln könnte, zahlt sich die Entkopplung durch MSW aber meist schon früh aus.