Métodos HTTP: semântica, segurança (safe) e idempotência
Em testes de API, não basta verificar se a resposta “funciona”: é essencial validar se o método HTTP usado é coerente com a intenção da operação e se os efeitos no estado do servidor seguem a semântica esperada. Isso reduz bugs, inconsistências e comportamentos perigosos (como um GET que altera dados).
Conceitos-chave para testar
- Semântica do método: o que o método significa (ler, criar, substituir, modificar parcialmente, remover).
- Segurança (safe methods): métodos que não devem alterar o estado do servidor quando chamados (ex.: GET). Em geral, HEAD e OPTIONS também são safe, mas aqui o foco é GET.
- Idempotência: repetir a mesma requisição N vezes deve produzir o mesmo estado final no servidor. A resposta pode variar (ex.: timestamps), mas o estado final deve ser equivalente.
| Método | Intenção | Safe? | Idempotente? | Efeito esperado no servidor |
|---|---|---|---|---|
GET | Consultar recurso(s) | Sim | Sim | Não altera estado |
POST | Criar (ou acionar processamento) | Não | Não (em geral) | Cria novo recurso ou inicia ação |
PUT | Substituir recurso (ou criar em URI conhecida) | Não | Sim | Recurso fica exatamente como o payload define |
PATCH | Atualização parcial | Não | Depende (pode ser, mas nem sempre) | Altera apenas campos/partes informadas |
DELETE | Remover recurso | Não | Sim (idealmente) | Recurso deixa de existir (ou fica marcado como removido) |
GET: leitura sem efeitos colaterais
Quando usar
- Buscar um recurso:
GET /users/123 - Listar recursos com filtros/paginação:
GET /users?status=active&page=2
Efeitos esperados no estado do servidor
- Não deve criar, atualizar ou remover dados.
- Não deve disparar ações com efeitos persistentes (ex.: “confirmar compra”, “enviar e-mail”).
Exemplo de requisição e resposta esperada
GET /users/123 HTTP/1.1
Host: api.exemplo.com
Accept: application/jsonHTTP/1.1 200 OK
Content-Type: application/json
ETag: "u123-v7"
{
"id": 123,
"name": "Ana",
"email": "ana@exemplo.com",
"status": "active"
}Checklist de validação (GET)
- Semântica: endpoint retorna representação do recurso/coleção.
- Safe: chamar o GET repetidas vezes não altera dados (ver passo a passo abaixo).
- Status codes:
200para sucesso;404se não existe;400para parâmetros inválidos;401/403para autenticação/autorização. - Cacheabilidade (quando aplicável): presença de
ETage suporte aIf-None-Matchretornando304 Not Modified. - Consistência: campos obrigatórios presentes e tipos corretos; ausência de dados sensíveis indevidos.
Passo a passo prático: como testar que GET não altera estado
- Escolha um recurso com estado observável (ex.:
status,updatedAt, contador, etc.). - Faça um GET e registre a resposta completa.
- Faça o mesmo GET novamente (idealmente 2–3 vezes).
- Valide que não houve alteração em campos que indicam mutação (ex.:
updatedAtnão muda; contadores não incrementam; não surgem novos registros). - Se existir endpoint de auditoria/logs, valide que não houve evento de escrita associado ao GET.
Observação: métricas internas (telemetria) podem ser atualizadas sem violar a semântica, mas qualquer alteração em dados de domínio (ex.: pedido, usuário, saldo) é um problema.
POST: criação e ações não idempotentes
Quando usar
- Criar um recurso em uma coleção:
POST /orders - Disparar uma ação/processamento:
POST /payments/charge(quando não se encaixa em CRUD simples)
Efeitos esperados no estado do servidor
- Normalmente cria um novo recurso (novo identificador).
- Pode iniciar um processamento assíncrono (gerando um “job”, evento, etc.).
Exemplo: criar recurso e retornar localização/identificador
POST /orders HTTP/1.1
Host: api.exemplo.com
Content-Type: application/json
Accept: application/json
{
"customerId": 10,
"items": [
{"sku": "ABC", "qty": 2}
]
}HTTP/1.1 201 Created
Content-Type: application/json
Location: /orders/987
{
"id": 987,
"status": "created",
"customerId": 10,
"items": [{"sku": "ABC", "qty": 2}]
}Checklist de validação (POST)
- Criação: retorna
201 Createdquando cria recurso; corpo incluiid(ou equivalente). - Location: header
Locationaponta para o novo recurso (quando aplicável). - Validação de entrada: payload inválido retorna
400com detalhes úteis; campos obrigatórios são exigidos. - Não idempotência: repetir o mesmo POST pode criar duplicados; se o sistema precisa evitar duplicidade, deve haver estratégia (ex.: chave de idempotência) e isso deve ser testado.
- Segurança: requer autenticação/autorização adequadas; não permite criação em nome de outro usuário sem permissão.
Passo a passo prático: como testar comportamento não idempotente (e duplicidade)
- Envie um POST válido e capture o
idretornado. - Envie o mesmo POST novamente.
- Verifique se um novo recurso foi criado (novo
id) ou se a API bloqueia duplicidade (ex.:409 Conflictou retorno do mesmo resultado via idempotency key, se existir). - Se a API tiver suporte a chave de idempotência (ex.: header
Idempotency-Key), repita o POST com a mesma chave e valide que o estado final não duplica (mesmoidou mesma operação reaproveitada).
PUT: substituição completa (e idempotência)
Quando usar
- Substituir completamente um recurso conhecido:
PUT /users/123 - Atualizar de forma que o servidor passe a refletir exatamente o payload enviado.
Efeitos esperados no estado do servidor
- O recurso final deve ser equivalente ao payload (campos ausentes podem ser removidos/resetados, dependendo do contrato).
- Repetir o mesmo PUT deve manter o mesmo estado final (idempotente).
Exemplo: substituir usuário
PUT /users/123 HTTP/1.1
Host: api.exemplo.com
Content-Type: application/json
Accept: application/json
{
"id": 123,
"name": "Ana Souza",
"email": "ana@exemplo.com",
"status": "active"
}HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 123,
"name": "Ana Souza",
"email": "ana@exemplo.com",
"status": "active"
}Checklist de validação (PUT)
- Substituição: após o PUT, um GET deve refletir exatamente o recurso atualizado.
- Idempotência: repetir o mesmo PUT não deve gerar mudanças adicionais (nem criar versões extras, nem duplicar efeitos).
- Campos ausentes: comportamento definido e testado (ex.: ausentes são removidos? mantidos? rejeitados?).
- Status codes:
200ou204em sucesso;404se o recurso não existe (ou201se o contrato permitir criação via PUT em URI conhecida).
Passo a passo prático: como testar idempotência do PUT
- Faça GET do recurso e guarde o estado atual.
- Execute PUT com um payload completo.
- Faça GET e valide que o estado corresponde ao payload.
- Execute o mesmo PUT novamente.
- Faça GET e valide que o estado permanece igual ao passo 3 (sem alterações adicionais).
PATCH: atualização parcial (e armadilhas de idempotência)
Quando usar
- Alterar apenas alguns campos sem enviar o recurso inteiro:
PATCH /users/123 - Operações parciais que não representam uma substituição completa.
Efeitos esperados no estado do servidor
- Somente os campos/partes enviados devem mudar; o restante deve permanecer igual.
- Idempotência depende do tipo de patch: “setar valor” tende a ser idempotente; “incrementar contador” não é.
Cenários práticos: PUT vs PATCH
| Cenário | Melhor escolha | Por quê |
|---|---|---|
Atualizar apenas status de um usuário | PATCH | Evita enviar o recurso inteiro; altera campo específico |
| Cliente possui representação completa e quer “sincronizar” o recurso | PUT | Substituição completa reduz ambiguidade |
| Atualizar perfil inteiro e remover campos que não existem mais | PUT | Semântica de substituição permite remoção/reset |
| Aplicar operação do tipo “incrementar pontos” | PATCH (não idempotente) | É uma operação parcial com efeito acumulativo |
Exemplo 1 (PATCH idempotente): setar um campo
PATCH /users/123 HTTP/1.1
Host: api.exemplo.com
Content-Type: application/json
Accept: application/json
{
"status": "inactive"
}HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 123,
"name": "Ana Souza",
"email": "ana@exemplo.com",
"status": "inactive"
}Repetir esse PATCH tende a manter o mesmo estado final (idempotente), pois “status=inactive” não acumula efeito.
Exemplo 2 (PATCH não idempotente): operação incremental
PATCH /users/123/points HTTP/1.1
Host: api.exemplo.com
Content-Type: application/json
Accept: application/json
{
"op": "increment",
"value": 10
}HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 123,
"points": 110
}Repetir a mesma requisição incrementa novamente (110, 120, 130...), portanto não é idempotente. Em testes, isso deve ser intencional e documentado.
Checklist de validação (PATCH)
- Parcialidade: campos não enviados permanecem inalterados (validar via GET antes/depois).
- Validação: rejeita campos desconhecidos ou tipos inválidos conforme contrato.
- Idempotência: identificar se o patch é do tipo “set” (idempotente) ou “operação” (pode não ser). Testar repetição e observar estado final.
- Conflitos: se houver controle de concorrência (ex.: ETag), validar
412 Precondition Failedquando o cliente atualiza com versão desatualizada.
Passo a passo prático: como testar PUT vs PATCH no mesmo recurso
- Faça GET do recurso e salve a representação completa.
- Execute PATCH alterando um único campo (ex.:
status). - Faça GET e valide que apenas esse campo mudou.
- Agora execute PUT com a representação completa original (ou uma nova completa) e valide que o recurso foi substituído conforme o payload.
- Se o contrato define comportamento para campos ausentes no PUT, envie um PUT omitindo um campo opcional e valide se ele foi removido/resetado ou mantido (conforme especificação).
DELETE: remoção e idempotência observável
Quando usar
- Remover um recurso:
DELETE /orders/987
Efeitos esperados no estado do servidor
- O recurso deve deixar de existir ou ficar inacessível (em soft delete, pode ficar marcado como removido).
- Repetir DELETE deve manter o estado final “removido” (idempotente), embora a resposta possa variar.
Exemplo de requisição e respostas esperadas
DELETE /orders/987 HTTP/1.1
Host: api.exemplo.com
Accept: application/jsonHTTP/1.1 204 No ContentApós isso:
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
GET /orders/987 HTTP/1.1
Host: api.exemplo.com
Accept: application/jsonHTTP/1.1 404 Not Found
Content-Type: application/json
{
"error": "not_found",
"message": "Order 987 not found"
}Checklist de validação (DELETE)
- Remoção: GET posterior não retorna mais o recurso (ou retorna estado “deleted”, conforme contrato).
- Status codes:
204(comum) ou200com corpo; para recurso inexistente, pode retornar404ou204(varia por contrato, mas deve ser consistente). - Idempotência: repetir DELETE não deve “ressuscitar” recurso nem causar efeitos adicionais.
- Autorização: apenas perfis permitidos podem remover.
Passo a passo prático: como testar idempotência do DELETE
- Crie (ou selecione) um recurso existente e confirme com GET.
- Execute DELETE e valide o status (
204ou200). - Execute GET e valide que não existe/está removido.
- Execute DELETE novamente e valide que o comportamento é consistente com o contrato (ex.:
404ou204), sem recriar nem alterar outros dados.
Roteiro de testes: segurança (safe) e idempotência por método
Teste de segurança (safe) para GET
- Execute GET repetido e compare:
updatedAt, contadores de domínio, listas de itens, saldos. - Se houver auditoria, valide ausência de eventos de escrita.
- Verifique que parâmetros de consulta não disparam ações (ex.:
?confirm=truenão deve confirmar nada).
Teste de idempotência para PUT e DELETE
- Repita a mesma requisição 2–3 vezes.
- Valide que o estado final do recurso é o mesmo após cada repetição.
- Se a resposta variar, garanta que a variação é aceitável (ex.: cabeçalhos de data), mas o estado persistido não muda.
Teste de (não) idempotência para POST e PATCH
- POST: repetir deve criar duplicados, a menos que exista mecanismo explícito para evitar (ex.: chave de idempotência). Teste ambos os comportamentos conforme contrato.
- PATCH: classifique o endpoint como “set” (tende a ser idempotente) ou “operação” (pode não ser). Repita e observe se acumula efeito.
Exemplos adicionais de respostas esperadas em cenários comuns
GET com recurso inexistente
GET /users/999999 HTTP/1.1
Host: api.exemplo.com
Accept: application/jsonHTTP/1.1 404 Not Found
Content-Type: application/json
{
"error": "not_found",
"message": "User not found"
}POST com validação falhando
POST /orders HTTP/1.1
Host: api.exemplo.com
Content-Type: application/json
Accept: application/json
{
"customerId": null,
"items": []
}HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": "validation_error",
"fields": {
"customerId": "required",
"items": "must_have_at_least_one_item"
}
}PUT com conflito de versão (quando há ETag)
PUT /users/123 HTTP/1.1
Host: api.exemplo.com
Content-Type: application/json
If-Match: "u123-v6"
{
"id": 123,
"name": "Ana Souza",
"email": "ana@exemplo.com",
"status": "active"
}HTTP/1.1 412 Precondition Failed
Content-Type: application/json
{
"error": "precondition_failed",
"message": "Resource version mismatch"
}