Eine Mock-API, die auf Wunsch Fehler liefert

Hol dir ein 500, ein 429 mit Retry-After, einen Timeout oder einen abgeschnittenen Body, wann immer deine Tests einen brauchen.

Beispiele zuletzt am gegen die API ausgeführt

Um zu testen, wie dein Code mit einer fehlschlagenden API umgeht, richte ihn auf einen Endpunkt, der absichtlich scheitert. https://api.testingapis.com ist kostenlos, braucht keinen Schlüssel und erlaubt Requests von jedem Origin. /api/status/500 gibt ein 500 zurück und /api/status/429 ein 429 mit Retry-After. Jeder Code von 200 bis 599 funktioniert. Hängst du ?error=503 an einen echten Endpunkt, scheitert er, und /api/chaos/retry/{key}anmelden zum Anzeigen scheitert eine festgelegte Anzahl Male, bevor es klappt. /api/chaos/timeoutanmelden zum Anzeigen hängt, bis dein Client aufgibt, und /api/chaos/partialanmelden zum Anzeigen schickt eine halbe Antwort. Hier jeder davon, mit der Antwort, die er liefert.

Welcher Endpunkt für welchen Fehler

Du willst testenRequestWas zurückkommt
Einen bestimmten StatuscodeGET /api/status/503Diesen Status, einen JSON-Body und die Header, die er normalerweise trägt
Einen echten Endpunkt, der scheitertGET /api/Products/1?error=500Den angeforderten Status, mit X-Simulated: true
Einen langsamen echten EndpunktGET /api/Products/1?delay=3Die normale Antwort nach N Sekunden (höchstens 10)
Retry-LogikGET /api/chaos/retry/my-key?succeedAfter=2anmelden zum AnzeigenZwei Fehlschläge, dann 200. X-Attempt zählt die Versuche
Zufällige FehlerGET /api/chaos/flaky?failRate=0.3anmelden zum Anzeigen503 mit Retry-After: 1 in etwa 30 % der Fälle
Ein Client-TimeoutGET /api/chaos/timeout?seconds=30anmelden zum AnzeigenNichts, bis die Zeit um ist (1 bis 60 Sekunden)
Einen abgeschnittenen BodyGET /api/chaos/partialanmelden zum AnzeigenDie Header versprechen 10.000 Bytes, etwa 64 kommen an, dann schließt die Verbindung
Echtes Rate LimitingGET /api/RateLimitanmelden zum Anzeigen429 nach 5 Requests in 60 Sekunden, mit Retry-After
Eine gewichtete MischungGET /api/status/random?codes=200:3,500:1200 dreimal so oft wie 500

Jeden HTTP-Statuscode zurückgeben

/api/status/{code} nimmt jede Methode an und antwortet mit dem Code aus dem Pfad. Es sendet auch die Header, die ein Client bei diesem Status erwarten würde: Retry-After bei 429 und 503, WWW-Authenticate bei 401, Location bei Weiterleitungen und Allow bei 405. So kannst du prüfen, dass dein Code sie liest und nicht nur auf die Zahl schaut.

Terminal
$ curl -i https://api.testingapis.com/api/status/429
HTTP/1.1 429 Too Many Requests
content-type: application/json; charset=utf-8
retry-after: 5

{"status":429,"statusText":"Too Many Requests","category":"client-error","message":"This is a 429 response. Use /api/status/{code} to request any status."}
GET/api/status/429

Einen echten Endpunkt scheitern lassen

Ein Test, der nur /api/status/500 aufruft, beweist wenig über die Ansicht, die Produkte lädt. Hänge error an den Request, den deine App ohnehin stellt, und dieser Aufruf scheitert, statt Daten zu liefern. ?delay=N wartet vorher N Sekunden, mit ?delay=3&error=503 prüfst du also, dass ein Ladezustand erscheint und dann einer Fehlermeldung weicht.

Terminal
$ curl -i "https://api.testingapis.com/api/Products/1?error=503"
HTTP/1.1 503 Service Unavailable
x-simulated: true

{"status":503,"error":"Service Unavailable","message":"Simulated error. Use ?error={code} to test different status codes.","simulated":true}
GET/api/Products/1?error=503

Beide Parameter funktionieren an jedem Endpunkt, auch bei Schreibzugriffen. Die vollständige Liste steht in der API-Dokumentation.

Retries testen, ohne zu raten

Zufällige Fehler machen Retry-Tests wackelig. /api/chaos/retry/{key}anmelden zum Anzeigen ist deterministisch: Mit succeedAfter=2 scheitern die ersten zwei Requests für einen Schlüssel, der dritte klappt. failStatus wählt den Fehlerstatus (429, 500, 502, 503 oder 504), und jede Antwort hat einen Header X-Attempt, ein Test kann also prüfen, wie viele Versuche dein Client gemacht hat. Nimm pro Testlauf einen neuen Schlüssel oder setz ihn mit DELETE zurück.

Terminal
$ curl -i "https://api.testingapis.com/api/chaos/retry/my-test?succeedAfter=2&failStatus=429"
HTTP/1.1 429 Too Many Requests
retry-after: 1
x-attempt: 1

$ curl -i "https://api.testingapis.com/api/chaos/retry/my-test?succeedAfter=2&failStatus=429"
HTTP/1.1 429 Too Many Requests
retry-after: 1
x-attempt: 2

$ curl -i "https://api.testingapis.com/api/chaos/retry/my-test?succeedAfter=2&failStatus=429"
HTTP/1.1 200 OK
x-attempt: 3

$ curl -X DELETE https://api.testingapis.com/api/chaos/retry/my-test
{"message":"Counter for 'my-test' reset after 3 attempt(s).","key":"my-test"}
Dieses Beispiel nutzt Endpunkte für angemeldete Mitglieder.AnmeldenKostenloses Konto erstellen

Hier ein Test für einen Client mit Retries, mit dem eingebauten Test-Runner von Node. Ein vollständiges fetchWithRetry mit exponentiellem Backoff und Retry-After-Unterstützung steht in der Lektion zu Timeouts, Retries und Rate Limits (auf Englisch).

JavaScript
import { test } from "node:test";
import assert from "node:assert/strict";
import { fetchWithRetry } from "./client.js"; // your code under test

test("retries a 503 and returns the eventual 200", async () => {
  const key = `ci-${Date.now()}`;
  const res = await fetchWithRetry(
    `https://api.testingapis.com/api/chaos/retry/${key}?succeedAfter=2`,
  );
  assert.equal(res.status, 200);
  assert.equal(res.headers.get("x-attempt"), "3");
});

test("gives up on a 400 without retrying", async () => {
  const res = await fetchWithRetry("https://api.testingapis.com/api/status/400");
  assert.equal(res.status, 400);
});
Dieses Beispiel nutzt Endpunkte für angemeldete Mitglieder.AnmeldenKostenloses Konto erstellen

Timeouts und abgebrochene Verbindungen

Manche Fehler erzeugen nie einen Statuscode. Dein Client muss sie selbst bemerken, und ein Skript, das nur res.ok prüft, sieht sie nie. Dieses Skript ruft fünf scheiternde Endpunkte auf und gibt aus, was fetch mit jedem macht:

failures.mjs
const BASE = "https://api.testingapis.com";

async function attempt(label, url, init) {
  try {
    const res = await fetch(url, init);
    const body = await res.text();
    console.log(label.padEnd(8), res.status, res.headers.get("retry-after") ?? "-", body.slice(0, 50));
  } catch (err) {
    console.log(label.padEnd(8), "threw", err.name, err.cause?.code ?? err.message);
  }
}

await attempt("500", `${BASE}/api/status/500`);
await attempt("429", `${BASE}/api/status/429`);
await attempt("error=", `${BASE}/api/Products/1?error=503`);
await attempt("timeout", `${BASE}/api/chaos/timeout?seconds=10`, { signal: AbortSignal.timeout(2000) });
await attempt("partial", `${BASE}/api/chaos/partial`);
Dieses Beispiel nutzt Endpunkte für angemeldete Mitglieder.AnmeldenKostenloses Konto erstellen
Ausgabe (Node 24)
500      500 - {"status":500,"statusText":"Internal Server Error
429      429 5 {"status":429,"statusText":"Too Many Requests","c
error=   503 - {"status":503,"error":"Service Unavailable","mes
timeout  threw TimeoutError The operation was aborted due to timeout
partial  threw TypeError UND_ERR_SOCKET

Der Timeout und der abgeschnittene Body werfen eine Ausnahme, statt eine Antwort zu liefern. Der Timeout wirft, weil AbortSignal.timeout(2000) nach zwei Sekunden aufgegeben hat. Ohne Signal hätte fetch die vollen zehn gewartet. Die Teilantwort wirft beim Lesen des Bodys, nachdem Status und Header gesund aussehend angekommen sind. curl meldet dasselbe Problem:

Terminal
$ curl -sS -o /dev/null https://api.testingapis.com/api/chaos/partial
curl: (18) end of response with 9936 bytes missing
Dieses Beispiel nutzt Endpunkte für angemeldete Mitglieder.AnmeldenKostenloses Konto erstellen

Ein echter Rate Limiter

/api/status/429 täuscht eine einzelne Antwort vor. /api/RateLimitanmelden zum Anzeigen ist ein echter Limiter mit festem Zeitfenster: fünf Requests pro 60 Sekunden je Client, mit herunterzählendem X-RateLimit-Remaining und einem 429 mit genauem Retry-After beim sechsten. Sende einen Header X-Client-Id, damit deine Tests ein eigenes Fenster bekommen, und POST /api/RateLimit/resetanmelden zum Anzeigen leert es. Die Rate-Limit-Referenz nennt jeden Header.

GET/api/RateLimit
Dieser Request nutzt einen Endpunkt für angemeldete Mitglieder.AnmeldenKostenloses Konto erstellen

Was du in deinem Client prüfen solltest

  • 4xx-Fehler außer 408 und 429 werden nicht wiederholt: Ein schlechter Request bleibt schlecht.
  • 429 und 503 warten auf Retry-After, statt sofort neu zu versuchen.
  • Retries hören nach einer Höchstzahl von Versuchen auf, und der letzte Fehler erreicht den Nutzer.
  • Jeder Request hat einen Timeout, und ein Timeout zeigt einen Fehler statt eines endlosen Spinners.
  • Ein Body, der sich nicht parsen lässt oder unvollständig ankommt, ist ein Fehler, auch mit Status 200.
  • Die message eines Fehler-Bodys wird angezeigt oder protokolliert, nicht verschluckt.