Idempotência aplicada em testes de API
Idempotência é a propriedade de uma operação produzir o mesmo resultado observável quando executada uma ou mais vezes com os mesmos parâmetros e no mesmo estado inicial. Em testes de API, isso significa verificar não só o status e o body, mas também os efeitos colaterais: criação de registros, alterações de saldo, envio de eventos, duplicidade de itens, e mudanças em campos como updatedAt ou version.
O que validar como “mesmo resultado observável”
- Estado do recurso: o recurso final deve ser igual após 1 ou N chamadas.
- Ausência de duplicidade: não pode haver criação duplicada, cobranças repetidas, múltiplos eventos idênticos etc.
- Resposta consistente: a API pode responder com códigos diferentes em repetições (ex.:
200vs204), mas deve manter a semântica e o estado final. - Metadados: alguns metadados podem mudar (ex.:
updatedAt) dependendo da implementação; o teste deve definir o que é aceitável e o que é sinal de efeito duplicado.
Testando PUT: repetição sem efeitos adicionais
Em geral, PUT é usado para substituir/atualizar um recurso em um identificador conhecido. A repetição do mesmo PUT deve manter o recurso no mesmo estado final.
Passo a passo prático
- Prepare um recurso existente (ou crie um e capture o
id). - Envie um PUT com um payload completo e determinístico.
- Leia o recurso (GET) e armazene o estado relevante (campos de negócio).
- Repita o mesmo PUT (mesmo payload).
- Leia novamente e compare os campos de negócio com o estado anterior.
- Verifique efeitos colaterais: contadores, logs de auditoria, eventos, ou qualquer endpoint auxiliar que evidencie duplicidade.
Exemplo de requisição
PUT /users/123 HTTP/1.1 Content-Type: application/json { "name": "Ana", "email": "ana@example.com", "status": "active" }Critérios de verificação
- Após a 1ª e 2ª chamada,
GET /users/123retorna os mesmos valores de negócio (name,email,status). - Não há criação de recursos adicionais relacionados (ex.: não cria “novo usuário”).
- Se existir
versionouupdatedAt, defina regra:- Estrito: não deve mudar em repetição idêntica (útil quando a API implementa detecção de no-op).
- Flexível: pode mudar, mas não pode gerar efeitos de negócio (ex.: não dispara novo evento de “status changed” se nada mudou).
Testando DELETE: repetição e “already deleted”
DELETE tende a ser idempotente: apagar algo duas vezes deve resultar em “não existe” sem causar novos efeitos. A resposta pode variar por implementação (ex.: 204 na primeira e 404 na segunda), mas o estado final deve ser consistente.
Passo a passo prático
- Garanta que o recurso existe.
- Envie
DELETE. - Valide que o recurso não está mais acessível (GET retorna “não encontrado” ou equivalente).
- Repita o
DELETE. - Valide que o estado permanece “não existe” e que não houve efeitos adicionais.
Critérios de verificação
- Após a primeira exclusão, o recurso não pode ser retornado como ativo.
- A segunda exclusão não pode “deletar outra coisa” nem alterar contadores indevidamente.
- Se houver deleção lógica (soft delete), valide que o marcador de deleção não alterna de forma incorreta.
POST idempotente com Idempotency-Key
POST normalmente não é idempotente, pois cria novos recursos. Em operações críticas (pagamentos, criação de pedidos, emissão de faturas), muitas APIs oferecem idempotência via header Idempotency-Key. A ideia: o cliente envia uma chave única por intenção de operação; se repetir a requisição (por timeout, retry, replay), o servidor retorna o mesmo resultado da primeira execução, sem duplicar efeitos.
Como a Idempotency-Key deve se comportar
- Mesma chave + mesmo endpoint + mesmo payload → mesma resposta (ou resposta equivalente) e nenhum efeito duplicado.
- Mesma chave + payload diferente → erro de conflito/validação (comum:
409ou422) para evitar ambiguidade. - Janela de retenção: a chave costuma expirar (minutos/horas). Testes devem considerar o TTL documentado.
Passo a passo prático: POST com retry por timeout
- Gere uma chave única (ex.: UUID) e fixe-a para o teste.
- Envie
POSTcomIdempotency-Key. - Simule um cenário de incerteza do cliente:
- timeout no cliente (não recebeu resposta), ou
- falha de rede após enviar a requisição.
- Reenvie o mesmo
POSTcom a mesmaIdempotency-Key. - Valide que não houve duplicidade: o
iddo recurso criado é o mesmo, ou o servidor retorna referência ao mesmo resultado.
Exemplo
POST /payments HTTP/1.1 Content-Type: application/json Idempotency-Key: 7f3b2c2e-1a2b-4c1f-9f8a-2d9b9b3f9a10 { "amount": 1000, "currency": "BRL", "customerId": "c-10" }Critérios de verificação para detectar efeitos duplicados
- Identificador único: a segunda resposta deve apontar para o mesmo
paymentId(ou mesmoorderId). - Listagem/consulta: ao buscar pagamentos do cliente, deve existir apenas um registro correspondente àquela intenção.
- Campos de auditoria: não deve haver duas entradas de “captura”/“cobrança” para a mesma chave.
- Eventos: se houver endpoint/stream de eventos, não deve haver duplicação do evento de criação.
Repetição, replays, timeouts e retries: como testar com segurança
Retry vs replay
- Retry: reenvio automático após falha transitória (timeout, 502/503, conexão reset).
- Replay: reenvio posterior (às vezes manual) de uma requisição já executada, podendo ocorrer fora da janela imediata.
Matriz prática de cenários de repetição
| Cenário | O que acontece | O que o teste deve verificar |
|---|---|---|
| Timeout no cliente, servidor processou | Cliente não sabe se criou/atualizou | Reenvio não duplica; retorna mesmo resultado (Idempotency-Key) ou mantém estado (PUT/DELETE) |
| Timeout no cliente, servidor não processou | Nenhum efeito ocorreu | Reenvio executa uma vez; estado final correto |
| Retry após 503 | Falha transitória | Operação completa sem duplicidade; logs/contadores consistentes |
| Replay fora do TTL da Idempotency-Key | Servidor pode não reconhecer a chave | Comportamento conforme contrato: pode criar novo recurso; teste deve validar a regra documentada |
| Mesmo Idempotency-Key com payload diferente | Ambiguidade | Erro de conflito/validação e nenhum efeito adicional |
Como simular timeouts e falhas
- Timeout do cliente: configure o client de teste com timeout baixo e chame um endpoint que pode demorar (ou use um parâmetro de atraso se existir em ambiente de teste).
- Falha após envio: em testes automatizados, é comum simular via proxy/fault injection (reset de conexão) ou interromper a leitura da resposta.
- Retries controlados: implemente retry no teste com política explícita (número máximo, backoff) para reproduzir comportamento real do consumidor.
Checks para detectar duplicidade (além do status/body)
- Invariantes numéricos: saldo, estoque, contagem de itens do carrinho, total do pedido.
- Unicidade: campos únicos (ex.:
externalReference,idempotencyKeyarmazenada) não podem aparecer duplicados. - Consultas por janela: buscar registros criados “nos últimos X minutos” e filtrar por atributos do payload.
- Idempotency-Key ecoada: algumas APIs retornam a chave ou um identificador de requisição; use isso para correlacionar.
Concorrência em testes de API: atualizações simultâneas e conflitos
Concorrência aparece quando dois clientes (ou duas threads) tentam modificar o mesmo recurso ao mesmo tempo. Sem controle, ocorre “lost update” (uma atualização sobrescreve a outra). Testes de concorrência devem validar que a API detecta conflitos ou fornece mecanismos para evitá-los.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
Cenário 1: duas atualizações simultâneas sem controle otimista
Objetivo do teste: identificar se a API sofre com lost update.
- Leia o recurso e obtenha o estado inicial.
- Dispare duas requisições de atualização em paralelo, cada uma alterando um campo diferente.
- Leia o recurso ao final.
Critérios de verificação:
- Se a API não oferece controle de versão, o resultado pode ser “última escrita vence”. O teste deve registrar esse comportamento e avaliar se é aceitável para o domínio.
- Se o domínio exige consistência forte (ex.: saldo), “última escrita vence” costuma ser bug.
Cenário 2: controle otimista com ETag e If-Match
Controle otimista evita sobrescritas silenciosas. O servidor fornece uma versão do recurso (frequentemente via ETag). O cliente envia If-Match com a ETag que ele leu; se o recurso mudou, o servidor rejeita com conflito (comum: 412 Precondition Failed ou 409 Conflict, dependendo do contrato).
Passo a passo prático
- GET no recurso e capture a
ETagda resposta. - Cliente A: envia atualização com
If-Match: <etag>e payload. - Cliente B: usando a mesma ETag antiga, envia outra atualização concorrente.
- Valide que uma das operações falha por conflito/precondição.
- Faça novo GET e confirme que o estado reflete apenas a atualização vencedora.
Exemplo
GET /profiles/55 HTTP/1.1 200 OK ETag: "v3" { "id": 55, "nickname": "ana", "bio": "..." } PUT /profiles/55 HTTP/1.1 Content-Type: application/json If-Match: "v3" { "bio": "bio atualizada" }Critérios de verificação:
- Uma atualização deve retornar sucesso e a outra deve retornar
412ou409(conforme contrato). - A atualização rejeitada não pode ter aplicado efeitos parciais.
- Após re-GET, a ETag deve ter mudado (ex.:
v4), indicando nova versão.
Como estruturar testes concorrentes
- Barreira de sincronização: preparar duas threads/processos para disparar requests no mesmo instante.
- Repetição: rodar o cenário várias vezes para aumentar chance de colisão.
- Isolamento: usar recursos dedicados por execução (ids únicos) para evitar interferência entre testes.
- Observabilidade: registrar timestamps e IDs de correlação para entender a ordem real.
Consistência eventual: como testar sem falsos negativos
Em arquiteturas distribuídas, a escrita pode ser aceita e propagada de forma assíncrona. Isso gera uma janela em que leituras (ou sistemas derivados como busca, relatórios, caches) ainda não refletem a atualização. Testes precisam distinguir “ainda não propagou” de “nunca vai propagar”.
Padrão de teste: assertiva com polling e timeout
- Execute a operação de escrita (criar/atualizar).
- Defina um timeout total (ex.: 10–60s, conforme SLO do sistema) e um intervalo (ex.: 200–1000ms).
- Faça polling em um endpoint de leitura/consulta até a condição ser verdadeira ou o timeout estourar.
- Se estourar, falha com evidência: últimas respostas, tempo decorrido e correlação.
Critérios de verificação em consistência eventual
- Condição final: o estado esperado deve aparecer dentro do timeout.
- Monotonicidade (quando aplicável): após aparecer, não deve “regredir” para estado anterior.
- Leituras diferentes: se houver múltiplas fontes (ex.: endpoint principal e endpoint de busca), valide cada uma com janelas/SLAs distintos.
Cenários comuns
- Criação e indexação: cria um item e ele só aparece na busca após alguns segundos.
- Atualização e cache: GET imediato retorna valor antigo por cache; após expiração/invalidação, retorna o novo.
- Eventos assíncronos: a escrita dispara processamento posterior (ex.: cálculo, enriquecimento). O teste deve aguardar o campo derivado aparecer.
Checklist de cenários e critérios de verificação
Idempotência
- PUT repetido: estado final igual; sem efeitos duplicados.
- DELETE repetido: estado final “não existe”; sem efeitos adicionais.
- POST com Idempotency-Key: reenvio retorna mesmo resultado; mesma chave com payload diferente gera erro; sem duplicidade em consultas.
Retries e timeouts
- Timeout com incerteza: reenvio não duplica; invariantes preservadas.
- Retry em falha transitória: operação completa uma vez; sem múltiplos registros/eventos.
Concorrência
- Atualizações simultâneas: detectar lost update ou validar estratégia de resolução.
- ETag/If-Match: conflito/precondição em versão antiga; nenhuma aplicação parcial.
Consistência eventual
- Polling com timeout: condição final aparece dentro da janela.
- Sem regressão: após convergir, leituras não voltam ao estado anterior.