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 testen | Request | Was zurückkommt |
|---|---|---|
| Einen bestimmten Statuscode | GET /api/status/503 | Diesen Status, einen JSON-Body und die Header, die er normalerweise trägt |
| Einen echten Endpunkt, der scheitert | GET /api/Products/1?error=500 | Den angeforderten Status, mit X-Simulated: true |
| Einen langsamen echten Endpunkt | GET /api/Products/1?delay=3 | Die normale Antwort nach N Sekunden (höchstens 10) |
| Retry-Logik | GET /api/chaos/retry/my-key?succeedAfter=2anmelden zum Anzeigen | Zwei Fehlschläge, dann 200. X-Attempt zählt die Versuche |
| Zufällige Fehler | GET /api/chaos/flaky?failRate=0.3anmelden zum Anzeigen | 503 mit Retry-After: 1 in etwa 30 % der Fälle |
| Ein Client-Timeout | GET /api/chaos/timeout?seconds=30anmelden zum Anzeigen | Nichts, bis die Zeit um ist (1 bis 60 Sekunden) |
| Einen abgeschnittenen Body | GET /api/chaos/partialanmelden zum Anzeigen | Die Header versprechen 10.000 Bytes, etwa 64 kommen an, dann schließt die Verbindung |
| Echtes Rate Limiting | GET /api/RateLimitanmelden zum Anzeigen | 429 nach 5 Requests in 60 Sekunden, mit Retry-After |
| Eine gewichtete Mischung | GET /api/status/random?codes=200:3,500:1 | 200 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.
$ 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."}/api/status/429Einen 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.
$ 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}/api/Products/1?error=503Beide 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.
$ 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"}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).
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);
});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:
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`);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_SOCKETDer 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:
$ curl -sS -o /dev/null https://api.testingapis.com/api/chaos/partial
curl: (18) end of response with 9936 bytes missingEin 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.
/api/RateLimitWas 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
messageeines Fehler-Bodys wird angezeigt oder protokolliert, nicht verschluckt.