O que é um contrato de API
Um contrato de API é o acordo sobre como duas partes trocam dados. Ele descreve rotas, métodos, parâmetros, cabeçalhos, campos, tipos, regras de preenchimento e respostas de erro. No dia a dia, frontend e backend dependem desse acordo para evoluir sem adivinhações.
O problema aparece quando o teste verifica apenas o comportamento de uma parte. O backend pode responder com nomeCompleto enquanto a tela procura nome. Os testes isolados continuam verdes, mas a integração real quebra quando o usuário abre a página.
Esse tema ganhou força porque aplicações modernas têm mais serviços, clientes e automações conversando entre si. Um contrato explícito reduz o espaço para interpretações diferentes e transforma uma mudança arriscada em uma decisão visível para toda a equipe.
Como funciona
O contrato começa pela requisição: método HTTP, caminho, autenticação, parâmetros, cabeçalhos e corpo. Depois, define a resposta de sucesso, os códigos de erro e o formato dos dados. Cada detalhe pode afetar a forma como uma tela renderiza ou como outro serviço continua seu fluxo.
Pense no contrato como o formulário de uma entrega. O frontend informa o endereço e os itens esperados; o backend confirma o resultado em um formato combinado. Se uma parte troca o nome de um campo ou passa uma data em formato inesperado, a entrega pode até sair, mas não chega corretamente ao destino.
Na prática, o acordo pode ser documentado em OpenAPI, JSON Schema ou nos próprios DTOs acompanhados de testes. O formato escolhido importa menos do que a regra principal: deve existir uma fonte clara para comparar o que foi prometido com o que realmente é enviado.
Comece pelo payload que atravessa a fronteira entre os sistemas. É ali que um teste unitário de cada lado deixa de ser suficiente.
Principais recursos de um contrato bem cuidado
Um bom contrato não é apenas uma lista de campos. Ele registra as decisões que evitam ambiguidades e facilita a revisão quando uma funcionalidade muda.
- Tipos explícitos: texto, número, booleano, lista e objeto não devem depender de interpretação.
- Obrigatoriedade clara: a equipe sabe quais campos sempre existem e quais podem ser nulos.
- Erros previsíveis: mensagens e códigos ajudam a tela a reagir sem gambiarras.
- Compatibilidade: mudanças quebráveis podem receber uma nova versão ou uma transição planejada.
Outro recurso importante é a validação automática. O pipeline pode comparar exemplos reais com um schema, executar testes de integração e verificar se uma alteração removeu ou renomeou algo que um cliente ainda usa.
O diferencial de tratar o contrato como código é a velocidade do feedback. A falha aparece perto do commit que causou o problema, e não depois que alguém percebe uma tela vazia durante o uso.
Como começar: instalação ou acesso passo a passo
Você não precisa adotar uma plataforma complexa no primeiro dia. Reúna o endpoint mais crítico, capture um exemplo real de requisição e resposta e escreva quais partes são obrigatórias. Depois, valide esse exemplo nos dois lados.
Um fluxo simples para começar é o seguinte:
- Escolha uma rota usada por uma tela importante.
- Registre o método, o caminho, os campos e os códigos de resposta.
- Crie um exemplo mínimo de sucesso e dois exemplos de erro.
- Adicione um teste que faça a requisição contra o backend real ou um ambiente de integração.
- Rode o mesmo contrato no pipeline antes de aceitar mudanças.
curl -s https://api.exemplo.com.br/usuários/42; npm testO comando acima é apenas um modelo de inspeção. Troque o endereço pelo endpoint real do seu projeto e nunca coloque tokens ou senhas no terminal, no teste ou no repositório.
Um mock precisa imitar o contrato real. Se ele sempre retorna dados perfeitos, pode esconder campos ausentes, status incorretos e respostas de erro que a aplicação não sabe tratar.
Exemplo prático
Imagine uma tela de perfil que envia um nome e recebe o usuário atualizado. O frontend espera displayName como texto e mostra uma mensagem quando a resposta tem status de sucesso. O backend, porém, foi alterado para devolver name e o teste do controller só verifica o status HTTP.
O teste do controller passa porque o endpoint continua respondendo 200. O teste do componente também passa porque o mock local ainda usa displayName. No navegador, a tela recebe um objeto válido, mas lê uma propriedade que não existe e exibe um nome vazio.
Um teste de contrato pega a falha na fronteira. Ele chama o endpoint de integração, valida o corpo recebido e compara o campo com o que o cliente consome. A correção pode ser restaurar o nome antigo, atualizar os dois lados no mesmo ciclo ou publicar uma versão compatível.
const resposta = await api.get('/usuários/42'); expect(resposta.status).toBe(200); expect(resposta.data).toHaveProperty('displayName'); expect(typeof resposta.data.displayName).toBe('string');O exemplo usa uma asserção simples, mas o princípio vale para qualquer stack. O teste deve verificar o formato que atravessa a aplicação, não apenas o fato de que uma função foi chamada ou que o servidor não retornou erro.
Comparação com alternativas
Testes unitários são ótimos para validar regras pequenas e rápidas. Eles ajudam a localizar a causa de uma falha, mas normalmente não confirmam que o payload produzido por um serviço combina com o payload consumido por outro.
Testes de integração exercitam uma fronteira maior e costumam revelar divergências de rota, autenticação, serialização e status. Já os testes de ponta a ponta mostram o impacto na jornada do usuário, embora sejam mais lentos e possam falhar por fatores externos.
- Unitário: use para regras puras e transformações locais.
- Contrato: use para garantir nomes, tipos e formatos entre consumidores e provedores.
- Integração: use para verificar a comunicação com banco, API e infraestrutura do ambiente.
- Ponta a ponta: use para confirmar os fluxos mais importantes na interface.
O ponto forte do teste de contrato é o equilíbrio entre proximidade da integração e feedback rápido. Ele não substitui os outros níveis, mas ocupa o espaço que costuma ficar descoberto quando cada equipe testa apenas seu próprio código.
Pontos positivos e limitações
A principal vantagem é detectar mudanças incompatíveis antes do deploy. O contrato também melhora a conversa entre pessoas de frontend, backend, QA e produto, porque a discussão passa a usar exemplos concretos.
Há ganhos de manutenção. Quando a API precisa evoluir, o time enxerga quais consumidores dependem do campo e pode planejar uma migração. A documentação fica mais confiável porque é confrontada com o comportamento real.
A limitação é que um contrato não garante uma boa regra de negócio. Ele pode confirmar que o campo existe e ainda assim permitir um valor errado para o contexto. Também existe custo inicial para organizar exemplos, ambiente e dados de teste.
Não transforme o contrato em uma cópia rígida de cada detalhe interno. Valide o que é necessário para o consumidor e deixe a implementação interna livre para evoluir.
Casos de uso reais
Em um painel administrativo, o contrato ajuda a garantir que filtros, paginação e totais tenham os mesmos nomes em todas as telas. Isso evita o cenário em que uma página envia pageSize e o servidor espera outro parâmetro.
Em um aplicativo mobile, a compatibilidade é ainda mais importante porque versões antigas podem continuar instaladas por muito tempo. Uma mudança no formato da resposta precisa considerar clientes que não serão atualizados imediatamente.
Em uma arquitetura com serviços, o contrato orienta integrações entre cobrança, notificações e cadastro. Cada serviço pode ter seu ciclo de deploy, mas nenhum deve descobrir a incompatibilidade apenas quando uma mensagem real atravessa a fila.
Para uma equipe pequena, o caso de uso mais valioso costuma ser a rota que sustenta o fluxo principal do produto. Um único teste de contrato nessa fronteira já pode evitar uma regressão cara e dar confiança para refatorar.
Dicas e boas práticas
Primeiro, mantenha exemplos de requisição e resposta próximos do teste. O exemplo deve ser legível para quem trabalha na tela e para quem mantém a API. Quando um campo muda, a revisão deve mostrar o impacto nos dois lados.
Prefira adicionar um campo opcional antes de remover ou renomear um campo usado. A transição gradual reduz o risco de quebrar clientes antigos.
Depois, faça o pipeline falhar com uma mensagem útil. Diga qual rota, campo, tipo ou status divergiu. Uma falha que aponta o payload esperado e o recebido economiza mais tempo do que um alerta genérico.
Separe testes de contrato por consumidor quando houver mais de uma aplicação. Assim, o backend conhece as necessidades reais sem aceitar regras que nenhum cliente usa.
Por fim, revise mocks e fixtures. Um mock antigo pode manter a suite verde enquanto o contrato real já mudou. Atualize os dados de teste junto com a alteração da API e inclua ao menos um cenário de erro relevante.
Vale a pena?
Sim, principalmente quando frontend e backend são mantidos por pessoas diferentes, quando há mais de um cliente ou quando uma API sustenta um fluxo crítico. O contrato reduz surpresas e dá uma rede de segurança para mudanças frequentes.
Talvez não seja a primeira prioridade para um script isolado com uma única função e nenhuma integração duradoura. Mesmo nesse caso, vale manter exemplos claros se o script puder crescer ou virar uma API usada por outras pessoas.
O próximo passo é escolher uma rota importante e escrever o primeiro teste na fronteira. Quando os testes passam e o contrato também passa, a equipe tem uma evidência muito mais forte de que a mudança funciona para quem realmente consome o sistema.
Comentários
Deixar um comentárioVocê precisa ter uma conta no Do Zero ao Junior para comentar.