Padrões de erro e mensagens em testes de API: consistência e rastreabilidade

Capítulo 7

Tempo estimado de leitura: 10 minutos

+ Exercício

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

Continue em nosso aplicativo e ...
  • Ouça o áudio com a tela desligada
  • Ganhe Certificado após a conclusão
  • + de 5000 cursos para você explorar!
ou continue lendo abaixo...
Download App

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: code string, message string, correlationId string, details array (quando presente).
  • Semântica: VALIDATION_ERROR significa validação em qualquer endpoint; RESOURCE_NOT_FOUND significa inexistência do recurso, etc.
  • Estabilidade: códigos internos e reason nã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-BR ou en-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 error como objeto.
  • error.code, error.message, error.correlationId são obrigatórios e strings não vazias.
  • error.details é opcional; quando presente, é array; cada item tem field, 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 error e campos obrigatórios.
  • Tipos: strings e arrays onde esperado.
  • Não vazio: code, message, correlationId nã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.correlationId deve 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.correlationId

6) 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-BR e depois com Accept-Language: en-US.
  • Valide que error.code e details[].reason são idênticos.
  • Valide que message e details[].message mudam 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

CategoriaCritérioComo validar
EstruturaResposta de erro segue envelope padrãoerror existe e é objeto
Campos obrigatórioscode, message, correlationId presentesAsserts de presença e string não vazia
TiposTipos consistentesdetails é array quando presente; itens com strings
EstabilidadeCódigos internos não mudam sem controleAsserts por cenário; snapshots de contrato quando aplicável
PadronizaçãoMesmo erro em endpoints diferentes tem mesma formaRodar mesma bateria em múltiplos endpoints e comparar estrutura/códigos
RastreabilidadeCorrelationId sempre presente e válidoRegex/parse; comparar com header se existir
Não vazamentoSem stack trace/SQL/segredosBusca por palavras-chave e padrões; lista de campos proibidos
i18nMensagens no idioma solicitado, códigos estáveisRequisições com Accept-Language e asserts de invariantes
GranularidadeValidação retorna erros por campo quando aplicáveldetails 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_ERROR para INVALID_REQUEST sem versionamento.
  • Mensagens “técnicas”: incluem nomes de exceções, tabelas ou stack trace.
  • Detalhes inconsistentes: às vezes details é objeto, às vezes array; ou field vem 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.

Agora responda o exercício sobre o conteúdo:

Ao testar o suporte a múltiplos idiomas em mensagens de erro de uma API, o que deve permanecer estável entre respostas em pt-BR e en-US para a mesma falha, e o que pode variar?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

Em testes de i18n, valide invariantes estáveis (error.code e details[].reason) iguais entre idiomas, e permita variação das mensagens (error.message e details[].message) conforme o idioma solicitado.

Próximo capitúlo

Contratos de API em testes (OpenAPI/Swagger): estrutura e validações

Arrow Right Icon
Capa do Ebook gratuito Testes de API: Conceitos Essenciais (REST, HTTP, Status Codes e Contratos)
47%

Testes de API: Conceitos Essenciais (REST, HTTP, Status Codes e Contratos)

Novo curso

15 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.