Mock-Server, Fixtures und Fehlerszenarien für stabile E2E-Suiten
Wer Zahlungs- und Versanddienstleister in jedem Testlauf live anspricht, riskiert Kosten, Rate Limits und zufällige Fehlschläge durch fremde Infrastruktur. Dieser Artikel zeigt, wie Mock-Server wie MSW und WireMock, aufgezeichnete Fixtures und gezielt simulierte Fehler und Timeouts eine E2E-Suite unabhängig von der Verfügbarkeit externer Dienste und trotzdem realistisch machen.
Inhaltsverzeichnis
- 1. Warum echte API-Aufrufe in Tests riskant sind
- 2. Mock Service Worker (MSW): Mocking auf Netzwerkebene
- 3. WireMock-artige Server-Mocks für Backend-Tests
- 4. Recording und Replay echter Responses als Fixtures
- 5. cy.intercept und page.route direkt im Testframework
- 6. Fehler- und Timeout-Szenarien gezielt simulieren
- 7. Contract Testing: Mocks gegen die echte API validieren
- 8. Testdaten-Management: Fixtures versionieren und pflegen
- 9. Wann live testen, wann mocken
- 10. Zusammenfassung
- 11. FAQ
1. Warum echte API-Aufrufe in Tests riskant sind
Ein Zahlungsanbieter-Sandbox-Account hat Rate Limits, ein Versanddienstleister berechnet pro Anfrage ein Kontingent, und ein E-Mail-Provider verschickt bei jedem Testlauf tatsächlich eine Mail. Wer diese Dienste in einer E2E-Suite direkt anspricht, koppelt die eigene Testlaufzeit und Zuverlässigkeit an die Infrastruktur eines fremden Anbieters. Fällt die Sandbox eines Zahlungsdienstleisters nachts wegen Wartungsarbeiten aus, schlagen plötzlich Dutzende Tests fehl, die mit dem eigenen Code nichts zu tun haben.
Hinzu kommen Seiteneffekte, die sich in einer Sandbox nur schwer sauber halten lassen: Testbestellungen, die reale Versandetiketten erzeugen, oder wiederholte Testzahlungen, die den Sandbox-Datenbestand mit Zeit immer unübersichtlicher machen. Rate Limits führen bei parallelisierten CI-Läufen zu 429-Antworten, die nichts mit einem echten Bug zu tun haben, aber die Suite trotzdem rot färben. Ein Team, das solche Fehlschläge oft genug sieht, beginnt fehlgeschlagene Tests reflexhaft zu ignorieren, und genau dann verliert die Suite ihren Zweck.
2. Mock Service Worker (MSW): Mocking auf Netzwerkebene
Mock Service Worker fängt HTTP-Anfragen auf Netzwerkebene ab, im Browser über einen Service Worker, in Node.js über eine Interception-Schicht, und beantwortet sie mit definierten Handlern. Der entscheidende Vorteil gegenüber dem Mocken von fetch oder axios direkt im Code: Die Anwendung selbst merkt nichts vom Mock, sie sendet einen echten Request, der lediglich nie das eigentliche Netzwerk verlässt. Das funktioniert transparent, unabhängig davon, welche HTTP-Bibliothek ein Frontend-Team einsetzt.
MSW eignet sich besonders für Fälle, in denen ein Payment-Widget oder ein Versandkosten-Rechner direkt im Browser gegen die externe API spricht, etwa ein eingebettetes Zahlungs-iFrame, das clientseitig Preise validiert. Handler werden als Liste von Request-Matchern mit zugehöriger Response-Funktion definiert und lassen sich pro Testfall gezielt überschreiben, sodass ein einzelner Test einen abweichenden Fehlerfall simulieren kann, ohne die globale Handler-Konfiguration zu verändern.
// src/mocks/handlers.js
import { http, HttpResponse, delay } from 'msw';
export const handlers = [
// Happy path: successful payment authorization
http.post('https://api.payment-provider.test/v2/authorize', async () => {
return HttpResponse.json({
status: 'authorized',
transactionId: 'txn_8f2c1a',
amount: 4990,
currency: 'EUR',
});
}),
// Shipping rate lookup for a Magento checkout step
http.get('https://api.shipping-provider.test/rates', ({ request }) => {
const url = new URL(request.url);
const zip = url.searchParams.get('zip');
if (!zip) {
return HttpResponse.json({ error: 'missing_zip' }, { status: 400 });
}
return HttpResponse.json({
rates: [{ carrier: 'DHL', service: 'standard', price: 4.99 }],
});
}),
];
3. WireMock-artige Server-Mocks für Backend-Tests
Nicht jeder externe API-Aufruf passiert im Browser. In einem Magento-Shop ruft häufig das PHP-Backend selbst den Zahlungsanbieter oder Versanddienstleister serverseitig auf, etwa im Payment-Modul während der Order-Placement-Pipeline. MSW hilft hier nicht, weil der Request nie den Browser durchläuft. Für diesen Fall braucht es einen eigenständigen Mock-Server nach dem WireMock-Prinzip: ein separater Prozess oder Container, der HTTP-Anfragen entgegennimmt und anhand konfigurierter Stub-Mappings beantwortet, während der Magento-Container per DNS- oder Konfigurations-Override auf diesen Mock-Server statt auf die echte API zeigt.
WireMock-Mappings werden als JSON-Dateien mit einem Request-Matcher, request, und einer zugehörigen Antwort, response, definiert und lassen sich über eine Admin-API zur Laufzeit dynamisch ändern. Das erlaubt Stateful-Szenarien, etwa eine erste Anfrage mit Erfolg und eine zweite identische Anfrage mit einem simulierten Serverfehler, um Retry-Logik zu prüfen. Für ein Magento-Testsetup läuft ein solcher Mock-Server meist als zusätzlicher Service in der Docker-Compose-Konfiguration der CI-Pipeline.
{
"request": {
"method": "POST",
"urlPath": "/v2/authorize",
"bodyPatterns": [
{ "matchesJsonPath": "$.currency", "equalTo": "EUR" }
]
},
"response": {
"status": 200,
"headers": { "Content-Type": "application/json" },
"jsonBody": {
"status": "authorized",
"transactionId": "txn_8f2c1a",
"amount": 4990,
"currency": "EUR"
},
"fixedDelayMilliseconds": 120
}
}
4. Recording und Replay echter Responses als Fixtures
Handgeschriebene Mock-Antworten enthalten fast immer subtile Abweichungen von der Realität: ein fehlendes Feld, ein anderer Datentyp, eine nicht bedachte Verschachtelung. Zuverlässiger ist es, echte Antworten einmalig gegen die Sandbox-Umgebung des Anbieters aufzuzeichnen und als Fixture zu speichern. WireMock unterstützt dafür einen Proxy-Aufnahme-Modus, der Anfragen an die echte API weiterleitet, die Antwort mitschneidet und automatisch als Mapping-Datei ablegt. Playwright bietet mit routeFromHAR() einen vergleichbaren Mechanismus über HTTP-Archive.
Der Vorteil gegenüber handgeschriebenen Fixtures liegt in der Formtreue: Header, verschachtelte Objekte und selbst unerwartete Zusatzfelder der echten API landen automatisch in der Fixture. Der Nachteil ist, dass eine einmal aufgezeichnete Fixture bei einer API-Änderung stillschweigend veraltet, ohne dass ein Test das bemerkt, solange der Mock weiterhin nur gegen sich selbst getestet wird. Aufgezeichnete Fixtures gehören deshalb ins Repository unter Versionskontrolle und sollten in regelmäßigen Abständen gegen die Sandbox neu aufgenommen werden.
{
"_recordedAt": "2026-06-02T09:14:00Z",
"_source": "sandbox.shipping-provider.test",
"request": { "method": "GET", "path": "/rates?zip=10115&country=DE" },
"response": {
"status": 200,
"body": {
"rates": [
{ "carrier": "DHL", "service": "standard", "price": 4.99, "etaDays": 2 },
{ "carrier": "DHL", "service": "express", "price": 12.5, "etaDays": 1 }
],
"requestId": "req_a91cf0"
}
}
}
5. cy.intercept und page.route direkt im Testframework
Für viele E2E-Tests genügt eine leichtere Lösung als ein separater Mock-Server: Sowohl Cypress mit cy.intercept() als auch Playwright mit page.route() können HTTP-Anfragen direkt im Testframework abfangen, ohne zusätzlichen Prozess. Das eignet sich immer dann, wenn der zu mockende Aufruf im Browser stattfindet, etwa ein clientseitiges Payment-SDK, das Preise vor dem Checkout validiert. Beide APIs erlauben es, pro Testfall eine spezifische Fixture oder Callback-Antwort zuzuweisen, ohne die Handler-Konfiguration des gesamten Projekts anzufassen.
Der wichtige Unterschied zu MSW oder WireMock: cy.intercept() und page.route() wirken nur innerhalb des laufenden Testframeworks und nur für Requests, die der Browser tatsächlich absetzt. Ruft das Backend eine externe API serverseitig auf, bleibt dieser Weg unsichtbar für das Testframework und muss stattdessen über einen serverseitigen Mock wie WireMock abgedeckt werden. In der Praxis kombinieren viele Magento-Projekte beide Ansätze: cy.intercept() für clientseitige Aufrufe, WireMock für serverseitige Payment- und Versandintegrationen.
6. Fehler- und Timeout-Szenarien gezielt simulieren
Gegen eine echte Sandbox lässt sich kaum zuverlässig erzwingen, dass ein Zahlungsanbieter mit 503 antwortet, eine Anfrage nach zehn Sekunden abbricht oder ein Response-Body plötzlich fehlerhaftes JSON enthält. Genau diese Szenarien sind aber entscheidend, um zu prüfen, ob eine Anwendung Fehler verständlich anzeigt, Retry-Logik greift und Timeout-Budgets eingehalten werden. Mit Mocking lassen sich solche Zustände deterministisch und ohne Zufallsfaktor erzwingen, jeder Testlauf produziert exakt denselben Fehlerfall.
cy.intercept() unterstützt dafür Optionen wie forceNetworkError für einen kompletten Verbindungsabbruch und delay für künstliche Latenz, um Timeout-Handling zu testen. Playwright erreicht dasselbe über route.abort() für Netzwerkfehler und eine verzögerte route.fulfill()-Antwort für Latenztests. Ein vollständiges Testszenario deckt typischerweise vier Fälle ab: Erfolg, fachlicher Fehler wie eine abgelehnte Zahlung, technischer Serverfehler wie 500, und Timeout, jeweils mit einer eigenen, klar benannten Fixture.
// cypress/e2e/checkout-payment-errors.cy.js
it('shows a clear error message when the payment gateway times out', () => {
cy.intercept('POST', '**/v2/authorize', (req) => {
req.reply({ delay: 15000, statusCode: 200, body: {} });
}).as('authorizeTimeout');
cy.visit('/checkout');
cy.get('[data-testid="place-order"]').click();
// The app should surface a timeout message before Cypress' own default timeout
cy.get('[data-testid="payment-error"]', { timeout: 12000 })
.should('contain.text', 'Zahlungsanbieter antwortet nicht');
});
it('shows a decline message on a rejected payment', () => {
cy.intercept('POST', '**/v2/authorize', {
statusCode: 402,
body: { status: 'declined', reason: 'insufficient_funds' },
}).as('authorizeDeclined');
cy.visit('/checkout');
cy.get('[data-testid="place-order"]').click();
cy.wait('@authorizeDeclined');
cy.get('[data-testid="payment-error"]').should('contain.text', 'abgelehnt');
});
7. Contract Testing: Mocks gegen die echte API validieren
Mocking löst das Problem echter, riskanter Aufrufe, schafft aber ein neues Risiko: Ein Mock kann beliebig lange grün bleiben, während sich die echte API längst geändert hat. Wenn ein Zahlungsanbieter ein Feld umbenennt oder einen neuen Pflichtparameter einführt, merkt eine rein gemockte Suite davon nichts, bis die Integration in Produktion tatsächlich bricht. Contract Testing schließt diese Lücke, indem es die Annahmen der Mocks regelmäßig gegen die reale API prüft, unabhängig von der schnellen, gemockten Hauptsuite.
Ein pragmatischer Einstieg ohne ein volles Consumer-Driven-Contract-Framework wie Pact ist ein separater, selten laufender Job, der die aktuellen Fixtures oder Mappings gegen ein JSON-Schema validiert, das aus der echten Sandbox-Antwort abgeleitet wurde. Läuft dieser Job wöchentlich statt bei jedem Commit, bleibt die Hauptsuite schnell und stabil, während Schema-Drift trotzdem innerhalb weniger Tage auffällt, statt erst bei einem Produktionsvorfall.
8. Testdaten-Management: Fixtures versionieren und pflegen
Fixtures gehören als Klartext-Dateien, JSON oder YAML, ins Repository unter Versionskontrolle, üblicherweise in einem dedizierten Ordner wie cypress/fixtures/payment/ oder tests/mocks/shipping/. Eine konsistente Namenskonvention pro Szenario, authorize-success.json, authorize-declined.json, authorize-timeout.json, macht auf einen Blick klar, welcher Fall welche Fixture verwendet, ohne den Dateiinhalt öffnen zu müssen. Änderungen an Fixtures sollten denselben Code-Review-Prozess durchlaufen wie Produktionscode, denn eine unbemerkt falsch angepasste Fixture kann einen ganzen Testfall unbemerkt bedeutungslos machen.
Eine häufige Falle ist die eine große, generische Erfolgs-Fixture, die für fast jeden Test wiederverwendet wird und dabei Randfälle wie unterschiedliche Währungen oder Beträge verschluckt. Besser sind kleine, gezielt parametrisierte Fixtures oder eine Factory-Funktion, die eine Basis-Fixture zur Laufzeit mit testspezifischen Werten überschreibt. So bleibt die Anzahl der Dateien überschaubar, während jeder Test trotzdem exakt die Werte prüft, die für seinen konkreten Fall relevant sind.
9. Wann live testen, wann mocken
Mocking sollte nicht bedeuten, dass eine Anwendung nie wieder gegen die echte API läuft. Ein kleiner, bewusst begrenzter Satz an Live-Smoke-Tests gegen die Sandbox, getrennt von der schnellen Hauptsuite und selten ausgeführt, bleibt wichtig, um echte Integrationsprobleme zu erkennen, die kein Mock je aufdecken kann. Die folgende Übersicht zeigt, wie sich diese Entscheidung in der Praxis typischerweise aufteilt.
| Testfall | Live-API-Aufruf | Mock-Ansatz | Empfehlung |
|---|---|---|---|
| Payment Happy Path bei jedem PR | Echter Sandbox-Call bei jedem Push | Aufgezeichnete Fixture via MSW/WireMock | Mocken, Sandbox nur nightly prüfen |
| Timeout- und Fehlerbehandlung | Gegen echte Sandbox kaum reproduzierbar | cy.intercept mit forceNetworkError/delay | Immer mocken |
| Versandkosten mehrerer Carrier | Rate Limits bei paralleler CI | WireMock-Stub pro Carrier-Response | Mocken, Wechsel via Contract-Test prüfen |
| Schema-Drift der Payment-API erkennen | Manuell, oft zu spät bemerkt | Contract-Test gegen echte Sandbox | Wöchentlich live prüfen |
| Erstintegration eines neuen Anbieters | Direkt gegen Sandbox entwickeln | Noch keine Fixtures vorhanden | Kurzzeitig live, danach recorden |
Mironsoft
E2E-Testautomatisierung und Mock-Server-Setups für Magento- und Hyvä-Shops
Eine E2E-Suite, die nicht an fremder Infrastruktur hängt?
Wir bauen belastbare Mock-Server-Setups mit MSW und WireMock, versionierte Fixture-Bibliotheken und Contract-Testing-Pipelines, damit eure Zahlungs- und Versandintegrationen zuverlässig getestet sind, ohne bei jedem Lauf echte Sandbox-Aufrufe zu riskieren.
MSW/WireMock Setup
Mock-Server für Payment- und Versand-APIs sauber in CI integrieren
Fixture-Bibliothek
Aufgezeichnete Responses versionieren und für Fehlerszenarien pflegen
Contract-Testing
Schema-Drift zwischen Mocks und echter API frühzeitig erkennen
10. Zusammenfassung
Echte Aufrufe gegen Zahlungsanbieter, Versanddienstleister und andere externe APIs in jedem Testlauf sind teuer, langsam und anfällig für Fehlschläge, die nichts mit dem eigenen Code zu tun haben. MSW mockt Anfragen auf Netzwerkebene für clientseitige Aufrufe, WireMock-artige Server-Mocks decken serverseitige Backend-zu-API-Kommunikation ab, und cy.intercept() beziehungsweise page.route() reichen für viele Fälle direkt im Testframework aus. Aufgezeichnete Fixtures statt handgeschriebener Antworten sorgen dafür, dass Mocks der Realität möglichst nahekommen.
Der größte Mehrwert liegt in Fehler- und Timeout-Szenarien, die sich gegen eine echte Sandbox kaum zuverlässig erzwingen lassen, mit Mocking aber deterministisch und reproduzierbar werden. Damit Mocks nicht unbemerkt von der Realität abdriften, braucht es ergänzend Contract Testing und ein bewusstes, kleines Set an echten Sandbox-Smoke-Tests. Wer diese Balance findet, bekommt eine E2E-Suite, die schnell, stabil und trotzdem realistisch bleibt.
Externe APIs mocken, Das Wichtigste auf einen Blick
Risiken echter Calls
Kosten, Rate Limits, Seiteneffekte und Flakiness durch fremde Infrastruktur in jedem Testlauf.
Mock-Strategien
MSW für clientseitige, WireMock für serverseitige Aufrufe, cy.intercept/page.route für schnelle Fälle.
Fehlerszenarien
Timeouts, 500er und Ablehnungen deterministisch simulieren, statt sie gegen die Sandbox zu erhoffen.
Contract Testing
Fixtures regelmäßig gegen die echte API validieren, damit Mocks nicht unbemerkt veralten.