O que são validações de contrato em APIs
Validação de contrato é a prática de verificar se uma API está entregando respostas que respeitam um acordo previamente definido entre quem consome e quem fornece o serviço. Esse acordo costuma ser expresso por regras como: quais campos devem existir, quais tipos esses campos devem ter, quais valores são permitidos, quais estruturas de objetos e listas são aceitas e como erros devem ser retornados. Em testes de API, validar contrato é diferente de validar “negócio”: você não está verificando se o cálculo do frete está correto, e sim se a resposta tem o formato esperado para que clientes (front-end, integrações, automações) não quebrem quando a API evoluir.
Na prática, validações de contrato ajudam a detectar mudanças involuntárias, como renomear um campo, trocar um tipo (por exemplo, número para string), remover um atributo que era obrigatório, ou alterar a estrutura de um objeto aninhado. Essas mudanças podem passar despercebidas se você testar apenas status code e alguns asserts pontuais. Um contrato bem validado reduz regressões e dá segurança para refatorações e versionamentos.
Campos obrigatórios: presença e consistência mínima
Uma validação de contrato frequentemente começa pelo básico: garantir que campos obrigatórios existem. “Obrigatório” aqui significa: o consumidor depende do campo para funcionar. Por exemplo, em uma resposta de “detalhe de pedido”, é comum que id, status, createdAt e items sejam essenciais. Se algum deles faltar, o cliente pode falhar, mesmo que o status code seja 200.
Há duas dimensões importantes ao validar obrigatoriedade: presença do campo e presença de valor. Um campo pode existir, mas vir como null ou string vazia. Dependendo do contrato, isso pode ser inválido. Por isso, ao definir “obrigatório”, deixe claro se o campo deve ser “presente e não nulo”, “presente e não vazio” ou apenas “presente”.
Exemplo de resposta para validar campos obrigatórios
Considere uma resposta JSON simplificada de um pedido:
Continue em nosso aplicativo
Você poderá ouvir o audiobook com a tela desligada, ganhar gratuitamente o certificado deste curso e ainda ter acesso a outros 5.000 cursos online gratuitos.
ou continue lendo abaixo...Baixar o aplicativo
{ "id": "ord_123", "status": "PAID", "createdAt": "2026-01-07T10:12:00Z", "customer": { "id": "cus_9", "email": "ana@exemplo.com" }, "items": [ { "sku": "SKU-1", "quantity": 2, "price": 19.9 } ], "total": 39.8}Campos obrigatórios típicos aqui: id, status, createdAt, customer.id, items, e dentro de cada item: sku, quantity, price. Note que “obrigatório” pode ser diferente em endpoints diferentes: em uma listagem, talvez customer.email não venha por performance, mas no detalhe venha.
Tipos: garantindo que o formato não muda silenciosamente
Depois de garantir presença, o próximo nível é validar tipos. Tipos errados são uma fonte comum de bugs: um campo que era número vira string, um boolean vira 0/1, uma data vira timestamp numérico, um objeto vira lista, e assim por diante. Mesmo que o valor “pareça” correto, o consumidor pode quebrar ao tentar tratar o dado.
Em JSON, os tipos básicos são: string, number, boolean, object, array e null. Em contrato, você normalmente quer restringir ainda mais: string com formato de data, string com padrão de e-mail, number inteiro, number com mínimo e máximo, array com itens de um tipo específico, objeto com propriedades obrigatórias e proibição de propriedades extras.
Armadilhas comuns de tipo
IDs numéricos vs strings: muitas APIs retornam
idcomo string para evitar problemas de precisão e padronizar. Se um endpoint retornar como number, pode quebrar clientes que concatenam ou armazenam como string.Datas: retornar
createdAtcomo string ISO-8601 em um endpoint e como timestamp em outro cria inconsistência. O contrato deve definir um formato único.Campos opcionais: um campo opcional pode ser omitido ou pode vir como
null. O contrato deve explicitar qual comportamento é aceito.Arrays vazios:
itemsdeve ser array mesmo quando vazio, e nãonullou objeto. Isso é parte do contrato.
Schemas JSON: validação estrutural e semântica
Para ir além de asserts manuais, você pode usar JSON Schema para descrever o contrato de uma resposta. JSON Schema é um padrão para definir a estrutura de um JSON: tipos, propriedades, obrigatoriedade, formatos, limites, enumerações e regras de composição. Com ele, você valida o “shape” completo do payload de forma consistente e escalável.
Um schema bem feito permite detectar rapidamente mudanças como: remoção de campo obrigatório, alteração de tipo, inclusão de propriedades inesperadas (quando você decide bloquear extras), e inconsistências em objetos aninhados. Além disso, schemas podem ser reutilizados entre requests e coleções, reduzindo duplicação.
Exemplo de JSON Schema para o pedido
Abaixo um schema simplificado para a resposta do pedido. Ele exige campos principais, valida tipos e restringe alguns formatos:
{ "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "additionalProperties": false, "required": ["id", "status", "createdAt", "customer", "items", "total"], "properties": { "id": { "type": "string", "minLength": 1 }, "status": { "type": "string", "enum": ["PENDING", "PAID", "CANCELED"] }, "createdAt": { "type": "string", "format": "date-time" }, "customer": { "type": "object", "additionalProperties": false, "required": ["id", "email"], "properties": { "id": { "type": "string", "minLength": 1 }, "email": { "type": "string", "format": "email" } } }, "items": { "type": "array", "minItems": 1, "items": { "type": "object", "additionalProperties": false, "required": ["sku", "quantity", "price"], "properties": { "sku": { "type": "string", "minLength": 1 }, "quantity": { "type": "integer", "minimum": 1 }, "price": { "type": "number", "minimum": 0 } } } }, "total": { "type": "number", "minimum": 0 } }}Alguns pontos importantes desse schema: additionalProperties: false impede campos extras. Isso é útil quando você quer detectar qualquer mudança no payload, mas pode ser rígido demais se a API evolui adicionando campos compatíveis. Uma alternativa é permitir extras no topo e ser mais rígido apenas em objetos críticos, ou validar apenas um subconjunto essencial.
Estratégias de contrato: rígido, tolerante e por camadas
Nem todo time escolhe o mesmo nível de rigidez. Em geral, você pode adotar três estratégias:
Contrato rígido: bloqueia campos extras e valida enumerações e formatos estritamente. Bom para APIs internas com forte governança e para detectar mudanças rapidamente. Pode gerar manutenção frequente quando a API adiciona campos.
Contrato tolerante: valida apenas campos essenciais e tipos principais, permitindo propriedades extras. Bom para APIs públicas ou em evolução, onde adicionar campos não deve quebrar consumidores.
Contrato por camadas: combina os dois. Por exemplo, valida rigidamente objetos internos usados pelo front-end e valida de forma tolerante metadados e campos opcionais.
Uma prática útil é separar “campos obrigatórios para o cliente” de “campos que a API pode adicionar sem impacto”. Assim, você evita falsos positivos e mantém o teste focado no que realmente quebra integrações.
Passo a passo prático: validando campos obrigatórios e tipos sem schema
Em alguns cenários, você pode começar com validações diretas de presença e tipo, especialmente quando o payload é pequeno ou quando você ainda não tem um schema consolidado. O objetivo aqui é criar um conjunto mínimo de garantias.
Passo 1: definir a lista de campos obrigatórios
Liste os campos que não podem faltar e, para objetos aninhados, defina o caminho. Exemplo: id, status, createdAt, customer.id, items, items[0].sku.
Passo 2: validar presença e não nulidade
Use asserts para garantir que o campo existe e não é null. Para strings, valide também minLength (não vazio). Para arrays, valide que é array e, se necessário, que tem ao menos um item.
Passo 3: validar tipos e formatos básicos
Valide que números são números, inteiros são inteiros, boolean é boolean. Para datas, valide padrão ISO-8601 com regex (quando não estiver usando schema). Para enum, valide que o valor está em uma lista permitida.
Exemplo de testes (sem schema) em JavaScript no Postman
const body = pm.response.json();pm.test("Contrato: campos obrigatórios no topo", () => { pm.expect(body).to.have.property("id"); pm.expect(body.id).to.be.a("string").and.not.empty; pm.expect(body).to.have.property("status"); pm.expect(body.status).to.be.a("string"); pm.expect(["PENDING","PAID","CANCELED"]).to.include(body.status); pm.expect(body).to.have.property("createdAt"); pm.expect(body.createdAt).to.be.a("string").and.not.empty; pm.expect(body).to.have.property("customer"); pm.expect(body.customer).to.be.an("object"); pm.expect(body.customer).to.have.property("id"); pm.expect(body.customer.id).to.be.a("string").and.not.empty; pm.expect(body).to.have.property("items"); pm.expect(body.items).to.be.an("array");});pm.test("Contrato: itens do pedido", () => { pm.expect(body.items.length).to.be.greaterThan(0); body.items.forEach((it, idx) => { pm.expect(it, `item[${idx}]`).to.have.property("sku"); pm.expect(it.sku).to.be.a("string").and.not.empty; pm.expect(it).to.have.property("quantity"); pm.expect(it.quantity).to.be.a("number"); pm.expect(Number.isInteger(it.quantity)).to.eql(true); pm.expect(it.quantity).to.be.at.least(1); pm.expect(it).to.have.property("price"); pm.expect(it.price).to.be.a("number"); pm.expect(it.price).to.be.at.least(0); });});Esse modelo é útil para começar, mas tende a crescer e ficar repetitivo. Quando você perceber que está copiando as mesmas validações em vários endpoints, é um sinal de que vale migrar para JSON Schema e reutilização de schemas.
Passo a passo prático: validação com JSON Schema no Postman
Para validar com JSON Schema no Postman, você normalmente usa uma biblioteca de validação. O Postman não inclui um validador completo de JSON Schema por padrão, então a abordagem mais comum é adicionar uma biblioteca como Ajv (Another JSON Schema Validator) via pm.sendRequest para buscar o script, ou embutir uma versão minificada no projeto, ou ainda usar um pacote já disponível no ambiente de execução do Postman quando aplicável. O ponto central do passo a passo é: definir o schema, carregar o validador e executar a validação, falhando o teste quando houver erros.
Passo 1: escrever o schema e decidir o nível de rigidez
Comece com um schema que cubra os campos essenciais. Decida se vai usar additionalProperties: false. Em APIs que evoluem com frequência, uma boa prática é começar tolerante e endurecer gradualmente em partes críticas.
Passo 2: armazenar o schema de forma reutilizável
Em vez de colar o schema em cada request, armazene-o em um local reutilizável: por exemplo, como uma variável (em nível de coleção) contendo o JSON do schema, ou como um arquivo referenciado em um fluxo de execução (quando sua estratégia de automação permitir). O objetivo é manter um único ponto de manutenção.
Passo 3: validar a resposta contra o schema
O teste deve: obter o JSON da resposta, compilar o schema e validar. Se inválido, exibir erros legíveis, indicando o caminho do campo que falhou.
Exemplo de validação com Ajv (padrão de uso)
O exemplo abaixo mostra o padrão de código. Dependendo do seu setup, você precisará garantir que o objeto Ajv esteja disponível (por exemplo, carregado previamente em scripts compartilhados).
// Exemplo de padrão: requer Ajv disponível no runtimeconst Ajv = require("ajv");const ajv = new Ajv({ allErrors: true, strict: false });const schema = { type: "object", required: ["id","status","createdAt","customer","items","total"], properties: { id: { type: "string", minLength: 1 }, status: { type: "string", enum: ["PENDING","PAID","CANCELED"] }, createdAt: { type: "string", format: "date-time" }, customer: { type: "object", required: ["id","email"], properties: { id: { type: "string", minLength: 1 }, email: { type: "string", format: "email" } } }, items: { type: "array", items: { type: "object", required: ["sku","quantity","price"], properties: { sku: { type: "string", minLength: 1 }, quantity: { type: "integer", minimum: 1 }, price: { type: "number", minimum: 0 } } } }, total: { type: "number", minimum: 0 } }};pm.test("Contrato: resposta segue JSON Schema", () => { const data = pm.response.json(); const validate = ajv.compile(schema); const ok = validate(data); if (!ok) { const errors = (validate.errors || []).map(e => `${e.instancePath} ${e.message}`).join(" | "); pm.expect.fail(`Schema inválido: ${errors}`); }});Repare em duas escolhas: allErrors: true para listar várias falhas de uma vez, e strict: false para reduzir quebras por detalhes de compatibilidade entre drafts e formatos. Em um ambiente mais controlado, você pode ativar modo estrito e padronizar o draft do schema.
Validações avançadas de contrato com JSON Schema
Enums, padrões e formatos
Use enum para estados e categorias. Use pattern para validar strings com regex (por exemplo, SKU). Use format para e-mail e date-time, lembrando que o suporte a format depende do validador e de plugins de formatos. Se o formato não estiver sendo verificado, você pode complementar com asserts específicos.
oneOf, anyOf e nullable
Às vezes um campo pode aceitar mais de uma forma. Exemplo: discount pode ser objeto quando aplicado e pode ser ausente quando não aplicado. Em schema, você pode modelar isso com oneOf ou anyOf. Para aceitar null, você pode definir type como array, por exemplo { "type": ["string","null"] }, ou usar convenções do seu draft/validador.
Regras condicionais
Contratos reais têm condicionais: se status é CANCELED, então canceledAt deve existir; se o pagamento é cartão, então card.last4 deve existir. JSON Schema suporta isso com if, then e else. Esse tipo de validação reduz a necessidade de muitos asserts manuais e documenta o contrato de forma executável.
Exemplo de condicional no schema
{ "type": "object", "required": ["status"], "properties": { "status": { "type": "string", "enum": ["PENDING","PAID","CANCELED"] }, "canceledAt": { "type": "string", "format": "date-time" } }, "if": { "properties": { "status": { "const": "CANCELED" } }, "required": ["status"] }, "then": { "required": ["canceledAt"] }}Como lidar com listas: contrato de itens e paginação
Endpoints de listagem costumam retornar arrays de objetos e, frequentemente, metadados de paginação. O contrato deve validar: o tipo do container (array ou objeto com data), o tipo de cada item, e a consistência dos metadados (por exemplo, page inteiro, pageSize inteiro, total número não negativo). Um erro comum é a API retornar data como objeto quando há apenas um resultado, em vez de array com um item; isso quebra consumidores que sempre iteram.
Em JSON Schema, valide o array com items e, se necessário, minItems. Para paginação, valide que data é array e que meta contém os campos esperados. Se a API permite lista vazia, não use minItems ou defina como 0.
Erros também têm contrato: padronização de respostas de falha
Validação de contrato não é só para respostas 200. Respostas de erro devem ser previsíveis: um consumidor precisa saber onde encontrar mensagem, código interno, detalhes por campo e correlação. Um contrato de erro típico inclui: error.code, error.message, error.details (opcional), e traceId (opcional). Validar isso evita que um endpoint retorne um HTML de erro, uma string solta, ou um formato diferente do padrão.
Uma abordagem prática é ter um schema de erro reutilizável e aplicá-lo a todos os testes que esperam falha (por exemplo, 400, 401, 403, 404, 422, 500). Assim, você garante consistência e melhora a diagnóstica.
Exemplo de schema de erro
{ "type": "object", "required": ["error"], "properties": { "error": { "type": "object", "required": ["code","message"], "properties": { "code": { "type": "string", "minLength": 1 }, "message": { "type": "string", "minLength": 1 }, "details": { "type": "array", "items": { "type": "object", "required": ["field","issue"], "properties": { "field": { "type": "string" }, "issue": { "type": "string" } } } } } }, "traceId": { "type": "string" } }}Boas práticas para manter schemas úteis e fáceis de evoluir
Comece pelo essencial e aumente cobertura
Se você tentar modelar 100% do payload desde o início, a manutenção pode ficar pesada. Comece com campos críticos e tipos, depois adicione regras como enum, formatos e condicionais. A cada bug real encontrado, fortaleça o schema naquela área.
Reutilize partes com definições
Quando vários endpoints compartilham estruturas (por exemplo, um objeto customer), crie schemas reutilizáveis e referencie com $ref quando seu validador e sua estratégia de armazenamento suportarem. Isso reduz divergências e facilita atualização.
Decida conscientemente sobre campos extras
additionalProperties: false é poderoso, mas pode gerar ruído quando a API adiciona campos compatíveis. Uma alternativa é permitir extras no topo e bloquear extras apenas em objetos internos críticos, ou usar testes separados: um teste “compatibilidade” tolerante e outro “contrato estrito” para ambientes controlados.
Mensagens de erro legíveis
Quando um schema falha, o valor do teste está no diagnóstico. Sempre que possível, agregue e imprima os erros com caminho do campo. Isso acelera a correção e evita que o teste vire apenas um “vermelho” sem contexto.