O Contract Testing com Pact verifica se consumidores e provedores de APIs mantêm expectativas compatíveis sem depender de um ambiente end-to-end completo. O consumidor descreve as interações que realmente utiliza, gera um contrato e o provedor executa uma verificação contra sua implementação.
Essa abordagem detecta mudanças incompatíveis antes do deploy, reduz testes integrados frágeis e documenta o uso real de endpoints. Pact é consumer-driven: o contrato nasce das necessidades do consumidor, não de uma cópia manual da especificação do provedor.
Neste guia, você aprenderá testes de consumidor, provider verification, matchers, provider states, Pact Broker, can-i-deploy, CI/CD, eventos, versionamento, segurança e boas práticas.
O que é Pact?
Pact é uma ferramenta de contract testing para HTTP e sistemas orientados a eventos. A documentação oficial do Pact JS apresenta PactV3, MatchersV3, mock server, provider verification e integração com Pact Broker. O guia geral em Pact Docs explica o fluxo consumer-driven.
Para documentar contratos públicos, consulte OpenAPI com Node.js. Para testes com dependências reais, veja Testcontainers no Node.js.
Problema dos testes end-to-end
Um teste que sobe consumidor, provedor, banco, fila e serviços externos pode falhar por muitos motivos:
- ambiente indisponível;
- dados compartilhados;
- versões incompatíveis;
- latência;
- dependência externa;
- ordem dos testes;
- configuração;
- rede.
Mesmo quando passa, pode não cobrir todas as expectativas de cada consumidor.
Fluxo do Pact
- O consumidor testa seu cliente contra um mock server Pact.
- O teste gera um arquivo pact.
- O contrato é publicado no Pact Broker.
- O provedor baixa os contratos relevantes.
- O provider verification executa cada interação.
- Os resultados são publicados.
- O pipeline consulta se a versão pode ser implantada.
Instalação
npm install --save-dev @pact-foundation/pactVerifique os requisitos de Node e plataformas da versão atual.
Cliente HTTP a testar
export class UserApiClient {
constructor(private readonly baseUrl: string) {}
async getUser(id: string): Promise<User> {
const response = await fetch(
`${this.baseUrl}/users/${id}`,
{
headers: {
accept: 'application/json'
}
}
);
if (!response.ok) {
throw new UserApiError(response.status);
}
return response.json();
}
}O teste Pact deve chamar o cliente real usado pela aplicação.
Teste de consumidor
import {
PactV3,
MatchersV3
} from '@pact-foundation/pact';
const pact = new PactV3({
consumer: 'orders-web',
provider: 'users-api',
dir: './pacts'
});
const { like, uuid, string } = MatchersV3;
it('busca um usuário', async () => {
pact
.given('o usuário 42 existe')
.uponReceiving('uma busca pelo usuário 42')
.withRequest({
method: 'GET',
path: '/users/42',
headers: {
accept: 'application/json'
}
})
.willRespondWith({
status: 200,
headers: {
'content-type': like('application/json')
},
body: {
id: uuid(),
name: string('Ana'),
plan: like('premium')
}
});
await pact.executeTest(async mockServer => {
const client = new UserApiClient(mockServer.url);
const user = await client.getUser('42');
assert.equal(user.plan, 'premium');
});
});O mock server verifica método, path, headers e resposta esperada.
Matchers
Matchers evitam contratos frágeis. Em vez de exigir um UUID específico, o contrato exige qualquer UUID válido.
like(): mesmo tipo e estrutura.string(): qualquer string.integer(): qualquer inteiro.uuid(): UUID.regex(): padrão.eachLike(): array com elementos semelhantes.
Use matchers apenas onde a variação é permitida.
Não deixe o contrato permissivo demais
Se qualquer campo usa like() sem restrições, uma mudança relevante pode passar. Defina valores exatos para enum, status, content type e códigos que fazem parte da lógica.
Provider state
.given('o usuário 42 existe')O state descreve a pré-condição, não como criá-la. O provider verification recebe o nome e prepara dados.
Estados claros
Prefira:
- “o pedido 123 está pendente”;
- “não existe usuário com o e-mail informado”;
- “o cliente possui plano premium”.
Evite states técnicos como “execute INSERT na tabela users”.
Provider verification
import { Verifier } from '@pact-foundation/pact';
await new Verifier({
provider: 'users-api',
providerBaseUrl: 'http://127.0.0.1:8080',
pactBrokerUrl: process.env.PACT_BROKER_URL,
pactBrokerToken: process.env.PACT_BROKER_TOKEN,
publishVerificationResult: true,
providerVersion: process.env.GIT_SHA,
stateHandlers: {
'o usuário 42 existe': async () => {
await resetDatabase();
await createUserFixture({ id: '42' });
}
}
}).verifyProvider();A API deve estar rodando localmente com dependências controladas.
Verificação contra pact local
Durante desenvolvimento, use pactUrls apontando para arquivos locais. No CI, use Broker para encontrar contratos.
Banco do provider
Testcontainers pode iniciar PostgreSQL ou MongoDB, aplicar migrations e preparar provider states. Isso valida o adapter real sem depender de ambiente compartilhado.
Isolamento de provider states
Cada interação deve começar em estado conhecido. Use truncate, rollback ou banco descartável. Não dependa da ordem de execução.
Autenticação
O verifier pode alterar requests antes de enviar para adicionar token de teste. Não grave tokens reais no pact.
requestFilter: (req, res, next) => {
req.headers.authorization = 'Bearer test-token';
next();
}A API ainda deve validar autenticação em testes próprios.
Pact Broker
O Broker armazena:
- pacts;
- versões de consumidor;
- versões de provedor;
- tags ou branches;
- environments;
- resultados de verificação;
- matriz de compatibilidade.
Publicando pact
Após os testes de consumidor:
pact-broker publish ./pacts \
--consumer-app-version "$GIT_SHA" \
--branch "$GIT_BRANCH"Use a CLI oficial ou biblioteca recomendada pela versão atual.
Versão imutável
Identifique builds com commit SHA. Não publique contratos diferentes com a mesma versão.
Branches e environments
Modelos modernos do Pact Broker usam branches e deploy records. Registre quando uma versão é implantada em test, staging ou production.
can-i-deploy
Antes do deploy, consulte se todos os contratos relevantes foram verificados:
pact-broker can-i-deploy \
--pacticipant orders-web \
--version "$GIT_SHA" \
--to-environment productionO comando precisa bloquear o deploy em incompatibilidade.
Webhook do Broker
Quando um consumidor publica contrato novo, o Broker pode disparar pipeline do provedor. Assim, a compatibilidade é verificada antes de o consumidor ser liberado.
Pending pacts
Pending pacts evitam quebrar o build principal do provedor quando surge uma expectativa nova ainda não implementada, mantendo o resultado visível. Configure a estratégia conforme o fluxo de branches.
WIP pacts
Work-in-progress pacts ajudam a verificar contratos recentes de branches sem exigir seleção manual. Siga as recomendações do Broker atual.
Mudança compatível
Adicionar campo opcional geralmente é compatível se consumidores ignoram campos extras. Remover ou alterar tipo de campo utilizado quebra o contrato.
Campos extras
Pact normalmente verifica apenas o que o consumidor declarou, permitindo que o provedor retorne campos adicionais. Isso reduz acoplamento.
Contrato não substitui testes do provider
Pact confirma expectativas dos consumidores conhecidos, mas não testa:
- todas as regras internas;
- segurança completa;
- performance;
- observabilidade;
- migrations;
- clientes desconhecidos;
- requisitos regulatórios.
OpenAPI versus Pact
- OpenAPI: contrato publicado pelo provedor.
- Pact: expectativas exercidas por consumidores.
As duas abordagens se complementam.
Contract tests versus integração
Pact verifica comunicação na borda. Um teste de integração ainda pode validar repository, fila ou provider externo. Não remova todos os testes end-to-end; mantenha poucos fluxos críticos.
Erros HTTP
Crie interações para:
- 200 ou 201;
- 404;
- 409;
- 422;
- 429;
- 503;
- timeout tratado pelo cliente.
O consumidor precisa testar seu comportamento, não apenas o caminho feliz.
Arrays
body: MatchersV3.eachLike({
id: MatchersV3.uuid(),
name: MatchersV3.string('Ana')
}, { min: 1 })Também teste lista vazia quando permitida.
Datas
Use matcher de regex ou timestamp conforme o contrato. Evite fixar uma data real sem necessidade.
Query parameters
Declare parâmetros e encoding esperados. Ordenação de query string pode variar; use a representação suportada pelo Pact em vez de string manual.
Headers
Não inclua headers instáveis como trace ID. Verifique apenas headers relevantes ao contrato, como content type, versionamento ou idempotency key.
GraphQL
Pact JS possui suporte e exemplos para GraphQL. O contrato precisa representar operação, variables e resposta usada pelo consumidor.
Mensagens e eventos
Pact também testa sistemas event-driven. O consumidor descreve a mensagem esperada; o provedor gera o evento para verificação.
Consulte Domain Events no Node.js.
Contrato de evento
{
"type": "order.approved",
"version": 1,
"eventId": "uuid",
"data": {
"orderId": "uuid",
"approvedAt": "timestamp"
}
}Teste schema, metadata e campos usados. Não acople o consumidor a campos irrelevantes.
Provider de mensagem
A verificação chama uma função que produz a mensagem para um provider state. Ela não precisa subir o broker, pois o foco é o payload.
Eventos e compatibilidade
Adicionar campo costuma ser seguro; renomear ou alterar semântica exige nova versão. Mantenha consumidores antigos durante a migração.
Mocks tradicionais
Um mock escrito manualmente pode divergir da API real. Pact transforma a expectativa em contrato verificável pelo provedor.
Não teste implementação do provider no consumidor
O consumidor declara comportamento observável. Não especifique SQL, framework, nome de classe ou detalhes internos.
Tamanho dos contratos
Contratos muito grandes podem indicar que o consumidor conhece campos demais. Use DTOs pequenos e endpoints coesos.
Ownership
Consumidor mantém testes de consumidor. Provedor mantém verificação e provider states. Ambas as equipes acompanham falhas de compatibilidade.
CI do consumidor
- executar testes;
- gerar pacts;
- publicar com SHA e branch;
- consultar can-i-deploy antes da promoção;
- registrar deploy.
CI do provedor
- subir API e dependências;
- buscar pacts relevantes;
- preparar provider states;
- verificar;
- publicar resultado;
- bloquear deploy incompatível;
- registrar deploy.
Segurança do Broker
- use TLS;
- token por CI;
- privilégio mínimo;
- rotação;
- não publique dados pessoais;
- proteja contratos internos;
- audite acessos.
Dados sensíveis no pact
Arquivos pact ficam armazenados e compartilhados. Use dados fictícios e matchers. Não inclua token, documento real ou segredo.
Observabilidade
Monitore:
- contratos publicados;
- verificações pendentes;
- falhas por provider;
- tempo até verificação;
- deploy bloqueado;
- pacts sem consumidor ativo;
- provider states lentos.
Limpeza de versões
Configure retenção de versões antigas sem remover dados necessários para ambientes ainda implantados.
Testes locais
Desenvolvedores devem executar consumer tests e provider verification localmente. O feedback não pode depender apenas do CI.
Debug
Quando falhar, compare:
- request recebido;
- request esperado;
- response real;
- matcher;
- provider state;
- versões do contrato.
Erros comuns
- Contrato manual: não representa o cliente real.
- Valores exatos demais: testes ficam frágeis.
- Matchers permissivos: quebra passa.
- Provider state compartilhado: verificação é instável.
- Sem Broker: compatibilidade não entra no deploy.
- Mesma versão republicada: matriz fica inconsistente.
- Apenas caminho feliz: cliente não testa erros.
- Segredo no pact: dado vaza.
Boas práticas
- Teste o cliente real.
- Use provider states semânticos.
- Use matchers precisos.
- Publique por SHA.
- Use branches e environments.
- Execute provider verification no CI.
- Bloqueie com can-i-deploy.
- Teste erros esperados.
- Proteja dados do contrato.
- Mantenha poucos E2E críticos.
Conclusão
O Contract Testing com Pact conecta testes do consumidor à verificação do provedor. As expectativas usadas pelo cliente se tornam contratos que impedem mudanças incompatíveis.
Com provider states isolados, matchers equilibrados, Pact Broker e can-i-deploy, a compatibilidade entra no pipeline. Pact não substitui OpenAPI, integração ou E2E, mas reduz a dependência de ambientes compartilhados e acelera a evolução de APIs.




