Por que padronizar erros em APIs
Em testes de API, respostas de erro são tão importantes quanto respostas de sucesso. Um padrão consistente permite que clientes (front-end, integrações, automações) tratem falhas de forma previsível, e que o time consiga rastrear incidentes com rapidez. “Consistência” significa que diferentes endpoints retornam a mesma estrutura de erro para a mesma categoria de problema (validação, autorização, recurso inexistente, conflito, erro interno), com campos estáveis e sem variações inesperadas.
“Rastreabilidade” significa que cada erro pode ser correlacionado a logs e traces do servidor por meio de um identificador (correlation id / trace id). Isso reduz o tempo para diagnosticar problemas, sem expor detalhes internos ao consumidor da API.
Formato recomendado de payload de erro
Um formato comum e testável é um objeto com: código interno, mensagem, detalhes (opcional), erros por campo (quando aplicável), e identificador de correlação. Abaixo, um exemplo genérico (independente de linguagem/framework):
{ "error": { "code": "VALIDATION_ERROR", "message": "Um ou mais campos são inválidos.", "correlationId": "2f6c2a7b-1d2a-4f1b-9b9e-6d2f0d3b3c1a", "details": [ { "field": "email", "reason": "INVALID_FORMAT", "message": "Informe um e-mail válido." }, { "field": "age", "reason": "MIN_VALUE", "message": "A idade deve ser maior ou igual a 18." } ] }}Campos e responsabilidades
- error.code: código interno estável, pensado para lógica do cliente e métricas (ex.:
VALIDATION_ERROR,RESOURCE_NOT_FOUND,CONFLICT,UNAUTHORIZED,FORBIDDEN,INTERNAL_ERROR). - error.message: mensagem legível para humanos, com granularidade adequada (útil sem expor detalhes internos).
- error.correlationId: id para rastrear a requisição nos logs/tracing. Deve existir em erros e, idealmente, também em sucessos (via header e/ou body).
- error.details: lista de problemas específicos. Para validação, costuma conter itens por campo; para outros erros, pode conter itens de contexto seguro (ex.: qual regra de negócio falhou) sem revelar dados sensíveis.
- details[].field: nome do campo (ou caminho, como
address.zipCode). - details[].reason: código estável por tipo de violação (ex.:
REQUIRED,INVALID_FORMAT,MAX_LENGTH). - details[].message: mensagem humana, possivelmente localizada (i18n).
Granularidade: mensagens úteis sem expor stack traces
Um erro “bom para o cliente” explica o que fazer a seguir (corrigir campo, tentar novamente, contatar suporte com correlationId) sem revelar implementação. Evite retornar: stack traces, nomes de tabelas, queries SQL, caminhos de arquivo, nomes de classes/métodos, chaves/segredos, tokens, detalhes de infraestrutura.
Exemplo de erro interno correto (genérico e rastreável):
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
{ "error": { "code": "INTERNAL_ERROR", "message": "Ocorreu um erro inesperado. Tente novamente mais tarde.", "correlationId": "9a1b0c2d-3e4f-5678-9abc-def012345678" }}Exemplo do que não deve acontecer (vazamento):
{ "error": { "code": "INTERNAL_ERROR", "message": "NullReferenceException at UserService.cs:142", "stackTrace": "...", "sql": "SELECT * FROM users WHERE ..." }}Padronização entre endpoints: o que deve ser igual
Para que testes sejam robustos e clientes não precisem de “tratamentos especiais”, padronize:
- Envelope: sempre
{ "error": { ... } }(ou outro padrão escolhido), sem alternar entre array, string e objeto dependendo do endpoint. - Nomes e tipos:
codestring,messagestring,correlationIdstring,detailsarray (quando presente). - Semântica:
VALIDATION_ERRORsignifica validação em qualquer endpoint;RESOURCE_NOT_FOUNDsignifica inexistência do recurso, etc. - Estabilidade: códigos internos e
reasonnão mudam sem versionamento/contrato; mensagens podem mudar (especialmente com i18n), mas devem manter sentido e não carregar dados sensíveis. - Localização do correlationId: idealmente também em header (ex.:
X-Correlation-Id), para facilitar observabilidade. Se estiver em header e body, ambos devem bater.
Internacionalização (i18n) e idioma: como testar
Quando a API suporta múltiplos idiomas, a resposta de erro deve respeitar o idioma negociado (por exemplo, via header Accept-Language). O ponto crítico em testes é separar o que é “estável” (códigos) do que é “variável” (mensagens traduzidas).
Estratégia recomendada
- Validar que códigos (
error.code,details[].reason) são iguais independentemente do idioma. - Validar que mensagens mudam conforme o idioma (quando aplicável) e não retornam “misturadas”.
- Validar fallback: se o idioma solicitado não existir, a API deve cair para um idioma padrão definido (ex.:
pt-BRouen-US). - Evitar testes frágeis que dependem de texto exato; prefira validar presença, idioma esperado (por exemplo, não conter termos do idioma errado) e/ou catálogo de mensagens quando houver contrato formal.
Exemplo prático: mesma validação em dois idiomas
Requisição inválida (ex.: campo obrigatório ausente). Em pt-BR:
{ "error": { "code": "VALIDATION_ERROR", "message": "Um ou mais campos são inválidos.", "correlationId": "c1b2c3d4-1111-2222-3333-444455556666", "details": [ { "field": "name", "reason": "REQUIRED", "message": "O nome é obrigatório." } ] }}Em en-US (mesmos códigos, mensagens traduzidas):
{ "error": { "code": "VALIDATION_ERROR", "message": "One or more fields are invalid.", "correlationId": "c1b2c3d4-1111-2222-3333-444455556666", "details": [ { "field": "name", "reason": "REQUIRED", "message": "Name is required." } ] }}Passo a passo prático: como testar padrões de erro
1) Defina um “contrato de erro” mínimo
Antes de automatizar, documente o mínimo obrigatório do payload. Exemplo de contrato mínimo:
- Resposta de erro deve ser JSON.
- Deve existir
errorcomo objeto. error.code,error.message,error.correlationIdsão obrigatórios e strings não vazias.error.detailsé opcional; quando presente, é array; cada item temfield,reason,message(strings).- Não deve existir
stackTrace,exception,sql,debug(ou qualquer campo proibido definido pelo time).
2) Monte uma matriz de cenários de erro por categoria
Crie casos que forcem a API a retornar cada categoria. Exemplos de categorias (ajuste ao seu domínio):
- Validação: campo obrigatório ausente, formato inválido, tamanho excedido.
- Recurso inexistente: buscar/alterar/deletar id que não existe.
- Conflito: criar recurso duplicado, violar regra de unicidade.
- Regra de negócio: transição de estado inválida (ex.: cancelar algo já finalizado).
- Erro interno: simular falha (ambiente de teste) para garantir que não vaza stack trace e que há correlationId.
3) Valide estrutura e tipos (schema-like) em cada cenário
Em cada resposta de erro, valide:
- Estrutura: existe
errore campos obrigatórios. - Tipos: strings e arrays onde esperado.
- Não vazio:
code,message,correlationIdnão podem ser vazios. - Campos extras: se sua política for “tolerante”, permita extras; se for “estrita”, falhe ao encontrar campos não previstos (útil para evitar vazamentos).
Exemplo de pseudo-validação (ideia, não dependente de ferramenta):
assert response.body.error is objectassert isNonEmptyString(response.body.error.code)assert isNonEmptyString(response.body.error.message)assert isNonEmptyString(response.body.error.correlationId)if (response.body.error.details exists): assert response.body.error.details is array for item in details: assert isNonEmptyString(item.field) assert isNonEmptyString(item.reason) assert isNonEmptyString(item.message)4) Valide estabilidade de códigos internos
Para automação confiável, o cliente deve depender de error.code e details[].reason (não do texto). Então, crie asserts que garantam que:
- Para o mesmo tipo de falha, o
error.codeé sempre o mesmo em endpoints diferentes. - Para a mesma regra de validação, o
reasoné sempre o mesmo (ex.:REQUIRED).
Exemplo: “campo obrigatório ausente” em /users e /customers deve retornar VALIDATION_ERROR e REQUIRED, mesmo que a mensagem humana varie.
5) Teste rastreabilidade: correlationId presente e consistente
Critérios práticos:
error.correlationIddeve existir em todas as respostas de erro.- Se a API também retorna correlation id em header (ex.:
X-Correlation-Id), valide que header e body são iguais. - Formato: se for UUID, valide regex/parse de UUID; se for outro formato, valide padrão acordado.
assert matchesUuid(response.body.error.correlationId)if (response.headers["X-Correlation-Id"] exists): assert response.headers["X-Correlation-Id"] == response.body.error.correlationId6) Teste não vazamento de informações sensíveis
Crie uma lista de “indicadores de vazamento” e valide que não aparecem em nenhum campo textual do erro (message e details). Exemplos de indicadores:
- Palavras-chave:
Exception,StackTrace,at(padrão de stack),SELECT,INSERT,ORA-,SQLSTATE. - Padrões: caminhos (
C:\,/var/), nomes de classes/pacotes (com.,org.), IPs internos, nomes de hosts. - Segredos: tokens JWT completos, chaves, credenciais, connection strings.
Exemplo de verificação simples (heurística):
for each textField in allTextFields(response.body.error): assert not containsAny(textField, ["stack", "Exception", "SELECT ", "SQLSTATE", "ORA-"]) assert not matchesJwtLike(textField) assert not matchesFilePath(textField)Além do body, verifique headers e mensagens de erro em campos alternativos (algumas APIs colocam detalhes em debug ou meta). Se existir modo debug em ambiente de teste, garanta que ele esteja desabilitado em ambientes que simulam produção.
7) Teste i18n quando aplicável
Passos:
- Envie a mesma requisição inválida com
Accept-Language: pt-BRe depois comAccept-Language: en-US. - Valide que
error.codeedetails[].reasonsão idênticos. - Valide que
messageedetails[].messagemudam para o idioma esperado (sem exigir texto exato, se o contrato não fixar). - Teste fallback com um idioma inexistente (ex.:
fr-FR) e valide que o idioma padrão é usado.
Exemplos de payloads por categoria (para usar como referência de testes)
Erro de recurso inexistente
{ "error": { "code": "RESOURCE_NOT_FOUND", "message": "Recurso não encontrado.", "correlationId": "5b7e2f4a-aaaa-bbbb-cccc-1234567890ab", "details": [ { "reason": "NOT_FOUND", "message": "Não existe usuário com o id informado." } ] }}Erro de conflito (duplicidade/regra de unicidade)
{ "error": { "code": "CONFLICT", "message": "Não foi possível concluir a operação devido a conflito.", "correlationId": "7c9d1e2f-aaaa-bbbb-cccc-abcdefabcdef", "details": [ { "field": "email", "reason": "ALREADY_EXISTS", "message": "Já existe um usuário com este e-mail." } ] }}Erro de regra de negócio (sem expor implementação)
{ "error": { "code": "BUSINESS_RULE_VIOLATION", "message": "Operação não permitida para o estado atual.", "correlationId": "11112222-3333-4444-5555-666677778888", "details": [ { "reason": "INVALID_STATE", "message": "Não é possível cancelar um pedido finalizado." } ] }}Critérios de aceitação (checklist) para testes automatizados
| Categoria | Critério | Como validar |
|---|---|---|
| Estrutura | Resposta de erro segue envelope padrão | error existe e é objeto |
| Campos obrigatórios | code, message, correlationId presentes | Asserts de presença e string não vazia |
| Tipos | Tipos consistentes | details é array quando presente; itens com strings |
| Estabilidade | Códigos internos não mudam sem controle | Asserts por cenário; snapshots de contrato quando aplicável |
| Padronização | Mesmo erro em endpoints diferentes tem mesma forma | Rodar mesma bateria em múltiplos endpoints e comparar estrutura/códigos |
| Rastreabilidade | CorrelationId sempre presente e válido | Regex/parse; comparar com header se existir |
| Não vazamento | Sem stack trace/SQL/segredos | Busca por palavras-chave e padrões; lista de campos proibidos |
| i18n | Mensagens no idioma solicitado, códigos estáveis | Requisições com Accept-Language e asserts de invariantes |
| Granularidade | Validação retorna erros por campo quando aplicável | details contém field/reason para cada violação |
Armadilhas comuns que seus testes devem capturar
- Formato diferente por endpoint: um retorna
{error:{...}}, outro retorna{message:"..."}. - Código interno instável: muda de
VALIDATION_ERRORparaINVALID_REQUESTsem versionamento. - Mensagens “técnicas”: incluem nomes de exceções, tabelas ou stack trace.
- Detalhes inconsistentes: às vezes
detailsé objeto, às vezes array; oufieldvem nulo. - CorrelationId ausente em falhas: justamente quando mais se precisa rastrear.
- i18n parcial: mensagem principal traduzida, mas mensagens de campo ficam no idioma padrão.