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 testarRequisiçãoO que volta
Um código de status específicoGET /api/status/503Esse status, um corpo JSON e os cabeçalhos que ele costuma trazer
Um endpoint real falhandoGET /api/Products/1?error=500O status que você pediu, com X-Simulated: true
Um endpoint real lentoGET /api/Products/1?delay=3A resposta normal depois de N segundos (máximo de 10)
Lógica de retryGET /api/chaos/retry/my-key?succeedAfter=2entre para verDuas falhas, depois 200. X-Attempt conta as tentativas
Falhas aleatóriasGET /api/chaos/flaky?failRate=0.3entre para ver503 com Retry-After: 1 em cerca de 30% das vezes
Um timeout do clienteGET /api/chaos/timeout?seconds=30entre para verNada até o tempo acabar (de 1 a 60 segundos)
Um corpo cortadoGET /api/chaos/partialentre para verOs cabeçalhos prometem 10.000 bytes, chegam uns 64, e a conexão fecha
Limite de taxa de verdadeGET /api/RateLimitentre para ver429 depois de 5 requisições em 60 segundos, com Retry-After
Uma mistura com pesosGET /api/status/random?codes=200:3,500:1200 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.

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

Fazer 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.

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

Os 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.

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"}
Este exemplo usa endpoints para membros conectados.EntrarCriar uma conta gratuita

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).

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);
});
Este exemplo usa endpoints para membros conectados.EntrarCriar uma conta gratuita

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:

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`);
Este exemplo usa endpoints para membros conectados.EntrarCriar uma conta gratuita
Saída (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

O 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:

Terminal
$ curl -sS -o /dev/null https://api.testingapis.com/api/chaos/partial
curl: (18) end of response with 9936 bytes missing
Este exemplo usa endpoints para membros conectados.EntrarCriar uma conta gratuita

Um 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.

GET/api/RateLimit
Esta requisição usa um endpoint para membros conectados.EntrarCriar uma conta gratuita

O 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-After em 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 message de um corpo de erro é mostrado ou registrado, não engolido.