Uma API simulada que devolve erros quando você quiser
Receba um 500, um 429 com Retry-After, um timeout ou um corpo truncado sempre que os seus testes precisarem.
Exemplos executados pela última vez contra a API em
Para testar como o seu código lida com uma API que falha, aponte-o para um endpoint que falha de propósito. https://api.testingapis.com é gratuita, não precisa de chave e aceita requisições de qualquer origem. /api/status/500 devolve um 500 e /api/status/429 um 429 com Retry-After. Qualquer código de 200 a 599 funciona. Adicionar ?error=503 a um endpoint real faz ele falhar, e /api/chaos/retry/{key}entre para ver falha um número definido de vezes antes de dar certo. /api/chaos/timeoutentre para ver trava até o seu cliente desistir, e /api/chaos/partialentre para ver envia meia resposta. Veja cada um, com a resposta que ele devolve.
Qual endpoint para cada falha
| Você quer testar | Requisição | O que volta |
|---|---|---|
| Um código de status específico | GET /api/status/503 | Esse status, um corpo JSON e os cabeçalhos que ele costuma trazer |
| Um endpoint real falhando | GET /api/Products/1?error=500 | O status que você pediu, com X-Simulated: true |
| Um endpoint real lento | GET /api/Products/1?delay=3 | A resposta normal depois de N segundos (máximo de 10) |
| Lógica de retry | GET /api/chaos/retry/my-key?succeedAfter=2entre para ver | Duas falhas, depois 200. X-Attempt conta as tentativas |
| Falhas aleatórias | GET /api/chaos/flaky?failRate=0.3entre para ver | 503 com Retry-After: 1 em cerca de 30% das vezes |
| Um timeout do cliente | GET /api/chaos/timeout?seconds=30entre para ver | Nada até o tempo acabar (de 1 a 60 segundos) |
| Um corpo cortado | GET /api/chaos/partialentre para ver | Os cabeçalhos prometem 10.000 bytes, chegam uns 64, e a conexão fecha |
| Limite de taxa de verdade | GET /api/RateLimitentre para ver | 429 depois de 5 requisições em 60 segundos, com Retry-After |
| Uma mistura com pesos | GET /api/status/random?codes=200:3,500:1 | 200 três vezes mais frequente que 500 |
Devolver qualquer código de status HTTP
/api/status/{code} aceita qualquer método e responde com o código do caminho. Ele também envia os cabeçalhos que um cliente esperaria com esse status: Retry-After em 429 e 503, WWW-Authenticate em 401, Location em redirecionamentos e Allow em 405. Assim você confere se o seu código os lê, e não só olha o número.
$ 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/429Fazer um endpoint real falhar
Um teste que só chama /api/status/500 não prova muita coisa sobre a tela que carrega produtos. Adicione error à requisição que o seu app já faz, e essa chamada falha em vez de devolver dados. ?delay=N espera N segundos antes, então ?delay=3&error=503 permite conferir que aparece um estado de carregamento e que depois ele dá lugar a uma mensagem de erro.
$ 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=503Os dois parâmetros funcionam em todos os endpoints, inclusive em escritas. A lista completa está na documentação da API.
Testar retries sem adivinhar
Falhas aleatórias deixam os testes de retry instáveis. /api/chaos/retry/{key}entre para ver é determinístico: com succeedAfter=2, as duas primeiras requisições para uma chave falham e a terceira dá certo. failStatus escolhe o status da falha (429, 500, 502, 503 ou 504), e toda resposta tem um cabeçalho X-Attempt, então um teste pode verificar quantas tentativas o seu cliente fez. Use uma chave nova a cada execução, ou zere-a com DELETE.
$ 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"}Aqui está um teste para um cliente que faz retry, com o test runner nativo do Node. Um fetchWithRetry completo, com backoff exponencial e suporte a Retry-After, está na lição de timeouts, retries e limites de taxa (em inglês).
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 e conexões quebradas
Algumas falhas nunca geram um código de status. O seu cliente precisa percebê-las sozinho, e um script que só confere res.ok nunca as vê. Este script chama cinco endpoints que falham e mostra o que o fetch faz com cada um:
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_SOCKETO timeout e o corpo cortado lançam exceção em vez de devolver uma resposta. O timeout lança porque AbortSignal.timeout(2000) desistiu depois de dois segundos. Sem um signal, o fetch teria esperado os dez. A resposta parcial lança ao ler o corpo, depois que o status e os cabeçalhos chegaram parecendo saudáveis. O curl aponta o mesmo problema:
$ curl -sS -o /dev/null https://api.testingapis.com/api/chaos/partial
curl: (18) end of response with 9936 bytes missingUm limitador de taxa de verdade
/api/status/429 simula uma única resposta. /api/RateLimitentre para ver é um limitador real de janela fixa: cinco requisições a cada 60 segundos por cliente, com X-RateLimit-Remaining diminuindo e um 429 com um Retry-After exato na sexta. Envie um cabeçalho X-Client-Id para que os seus testes tenham a própria janela, e POST /api/RateLimit/resetentre para ver a zera. A referência do limite de taxa tem todos os cabeçalhos.
/api/RateLimitO que verificar no seu cliente
- Erros 4xx que não sejam 408 nem 429 não são repetidos: uma requisição ruim continua ruim.
- 429 e 503 esperam o
Retry-Afterem vez de tentar de novo na hora. - Os retries param depois de um número máximo de tentativas, e o último erro chega ao usuário.
- Toda requisição tem um timeout, e um timeout mostra um erro em vez de um spinner infinito.
- Um corpo que não pode ser lido ou chega incompleto é um erro, mesmo com status 200.
- O
messagede um corpo de erro é mostrado ou registrado, não engolido.