Externe APIs in Tests mocken statt echte Aufrufe zu riskieren
PASS
expect()
API-Mocking · MSW · WireMock · E2E-Testing
Externe APIs in Tests mocken statt echte Aufrufe zu riskieren
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.

13 Min. Lesezeit MSW · WireMock · cy.intercept Fixtures · Contract Testing · Magento 2.4.8

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.

11. FAQ: Externe APIs in Tests mocken

1Warum externe APIs in E2E-Tests mocken?
Echte Aufrufe kosten Zeit, unterliegen Rate Limits und können durch fremde Ausfälle fehlschlagen, ohne dass der eigene Code fehlerhaft ist. Mocking entkoppelt Testzuverlässigkeit von fremder Verfügbarkeit.
2MSW vs. WireMock, was ist der Unterschied?
MSW fängt Requests im Browser oder Node.js ab, eignet sich für clientseitige Aufrufe. WireMock ist ein eigener Server-Prozess für serverseitige Backend-zu-API-Aufrufe.
3Wie zeichnet man echte Responses als Fixture auf?
Ein Proxy-Aufnahme-Modus oder ein HAR-Mechanismus wie routeFromHAR leitet Anfragen an die echte Sandbox weiter, zeichnet die Antwort auf und speichert sie für spätere Replays.
4Wie simuliert man einen Timeout?
In Cypress über cy.intercept mit delay in req.reply, in Playwright über eine verzögerte route.fulfill-Antwort. Beides erzwingt deterministisch eine lange Antwortzeit.
5Was ist Contract Testing?
Regelmäßige Prüfung, ob die Annahmen der Mocks noch zur echten API passen, etwa via Schema-Validierung. Verhindert, dass eine gemockte Suite grün bleibt, während die Integration bricht.
6Braucht man trotzdem echte Sandbox-Tests?
Ja, ein kleiner, selten laufender Satz an Live-Smoke-Tests bleibt wichtig, um echte Integrationsprobleme zu erkennen, ohne die schnelle Hauptsuite zu verlangsamen.
7Wie hält man Fixtures aktuell?
Fixtures versioniert ins Repository, denselben Code-Review wie Produktionscode, regelmäßige Neuaufnahme gegen die Sandbox und ergänzendes Contract Testing.
8Risiko einer zu generischen Fixture?
Eine einzige, überall wiederverwendete Erfolgs-Fixture verschluckt Randfälle wie Währungen oder Beträge. Kleinere, parametrisierte Fixtures bilden reale Variation genauer ab.
9Unterschied zu cy.intercept und page.route?
cy.intercept und page.route wirken nur im laufenden Testframework für Browser-Requests. MSW und WireMock laufen unabhängig und decken auch serverseitige Aufrufe ab.
10Braucht man noch einen Sandbox-Account?
Ja, für die initiale Fixture-Aufnahme, gelegentliche Contract-Tests und neue Integrationen. Für den täglichen Testbetrieb übernehmen Mocks die Mehrheit der Aufrufe.