Skip to content

Repository files navigation

Pact Provider-Consumer Contract Test

Menu


EN - English

This project demonstrates Pact contract testing between a provider and a consumer in TypeScript.

Why contract testing?

In a distributed system, API tests can confirm that a provider works the way its own implementation says it should. What they can't tell you is whether the API still does what its consumers expect.

Contract testing fills that gap. The consumer describes the interactions it depends on, and every change to the provider gets checked against those expectations.

For example, say the consumer expects:

{
  "id": 1,
  "name": "John",
  "email": "john@example.com"
}

But then the provider starts returning this instead:

{
  "userId": 1,
  "fullName": "John",
  "email": "john@example.com"
}

To the provider, everything looks fine. Its tests pass and its endpoints work. But the consumer just broke, because id and name are gone. That's the problem Pact exists to solve.

Why not API testing?

API testing asks one question: does the provider follow its own business and implementation rules? That's validation from the inside out.

Contract testing asks another: does the provider still give consumers what they expect? That's validation from the outside in.

To be clear, Pact does not replace API testing. A contract check only confirms that responses match what consumers expect; it says nothing about whether the behavior itself is correct. An endpoint can pass every contract verification and still be functionally wrong. Both layers matter: API tests prove the provider does the right thing, while contract tests prove it keeps doing it in a way consumers can rely on.

Scope

This project focuses on contract testing. Other quality concerns are intentionally handled by separate projects and test layers:

Structure

  • provider/: Express API exposing the /users routes used throughout the contract examples
    • src/routes/users.ts: route handlers that receive requests, talk to the repository, and shape the JSON responses the contract describes
    • src/repository/UserRepository.ts: in-memory data layer, and that's intentional. Database integration is outside this project's scope; the goal is to isolate the consumer-provider contract and demonstrate Pact verification without introducing unrelated infrastructure concerns
  • consumer/: UserClient, the class that calls the provider's users endpoints; it's the code whose expectations the Pact contract captures
  • pacts/: the generated Pact contract files, produced by the consumer tests and consumed by the provider verification
  • consumer/tests/, provider/tests/: the contract tests on each side; consumer tests generate the pact, provider tests verify against it

How it works

Consumer
   │
   │  "I need this"
   ▼
Pact Contract
   │
   ▼
Provider
   │
   │  "I still satisfy this"
   ▼
Verification
  1. The consumer runs its tests against a mocked provider and records every interaction it depends on. Those interactions become the Pact contract: "this is what I need from you."
  2. The provider verification process loads that contract, replays each recorded request against the real provider implementation, and verifies the response against the consumer's expectations.
  3. When all expectations match, verification passes and both sides can evolve independently, without breaking each other.

Breaking change detection

When a provider change breaks something a consumer depends on, provider verification fails.

For example, if the consumer expects a name field and the provider removes or renames it, the generated contract stays exactly as it was: it reflects what the consumer needs, not what the provider now returns. When verification runs against that unchanged contract, the mismatch appears immediately, and the build goes red before anything ships.

That's the point: the contract works as a safety net that catches breaking changes in CI, instead of letting consumers discover them at runtime.

Commands

# Install dependencies
npm install

# Start provider API (Express on port 4000)
npx ts-node provider/src/index.ts

# Run consumer tests (generates pact file)
npm run test:consumer

# Run provider verification (validates against pact file)
npm run test:provider

# Run both
npm run test

GitHub Actions

The project includes a CI workflow (.github/workflows/contract_tests.yml) that runs on push and pull requests to main:

  • Sets up Node.js 22 with npm caching
  • Installs dependencies with npm ci
  • Runs consumer tests to generate Pact files
  • Runs provider verification against the generated Pact files

Provider routes

Method Route Description
GET /users List all users
GET /users/:id Get user by ID
POST /users Create user (validates email, uniqueness)

PT-BR - Português

Este projeto demonstra testes de contrato Pact entre um provider e um consumer em TypeScript.

Por que contract testing?

Em um sistema distribuído, testes de API conseguem confirmar que o provider funciona como a própria implementação manda. O que eles não mostram é se a API ainda faz o que os consumers esperam dela.

O contract testing fecha essa lacuna. O consumer descreve as interações das quais depende, e cada mudança no provider é conferida contra essas expectativas.

Por exemplo, digamos que o consumer espera:

{
  "id": 1,
  "name": "John",
  "email": "john@example.com"
}

Mas aí o provider passa a devolver isto:

{
  "userId": 1,
  "fullName": "John",
  "email": "john@example.com"
}

Para o provider, está tudo bem: os testes passam e os endpoints funcionam. Só que o consumer quebrou, porque id e name sumiram. É esse problema que o Pact existe para resolver.

Por que não testes de API?

O teste de API pergunta: o provider segue suas próprias regras de negócio e implementação? Isso é validar de dentro para fora.

O contract testing pergunta outra coisa: o provider ainda entrega aos consumers o que eles esperam? Isso é validar de fora para dentro.

Vale deixar claro: o Pact não substitui os testes de API. Uma verificação de contrato só confirma que as respostas têm o formato que os consumers esperam; ela não diz nada sobre se o comportamento em si está correto. Um endpoint pode passar por todas as verificações de contrato e ainda assim estar funcionalmente errado. As duas camadas importam: os testes de API provam que o provider faz a coisa certa, enquanto os testes de contrato provam que ele continua entregando isso de um jeito em que os consumers podem confiar.

Escopo

Este projeto foca em contract testing. As demais preocupações de qualidade ficam intencionalmente sob responsabilidade de projetos e camadas de teste separados:

Estrutura

  • provider/: API Express que expõe as rotas /users usadas nos exemplos de contrato
    • src/routes/users.ts: handlers que recebem as requisições, conversam com o repositório e montam as respostas JSON descritas no contrato
    • src/repository/UserRepository.ts: camada de dados em memória, uma escolha intencional. Integração com banco de dados está fora do escopo deste projeto; o objetivo é isolar o contrato entre consumer e provider e demonstrar a verificação do Pact sem introduzir preocupações de infraestrutura sem relação com o tema
  • consumer/: UserClient, a classe que chama os endpoints de usuários do provider; é dela que saem as expectativas capturadas no contrato Pact
  • pacts/: os arquivos de contrato Pact gerados, produzidos pelos testes do consumer e consumidos pela verificação do provider
  • consumer/tests/, provider/tests/: os testes de contrato dos dois lados; os testes do consumer geram o pact, os do provider verificam contra ele

Funcionamento

Consumer
   │
   │  "Preciso disso"
   ▼
Pact Contract
   │
   ▼
Provider
   │
   │  "Ainda atendo isso"
   ▼
Verification
  1. O consumer roda seus testes contra um provider simulado e registra cada interação da qual depende. Essas interações viram o contrato Pact: "é isso que eu preciso de você".
  2. O processo de verificação do provider carrega esse contrato, repete cada requisição registrada contra a implementação real do provider e verifica as respostas contra as expectativas do consumer.
  3. Quando todas as expectativas batem, a verificação passa e os dois lados podem evoluir de forma independente, sem quebrar um ao outro.

Detecção de breaking changes

Quando uma mudança no provider quebra algo de que um consumer depende, a verificação do provider falha.

Por exemplo, se o consumer espera um campo name e o provider remove ou renomeia esse campo, o contrato gerado continua exatamente igual: ele reflete o que o consumer precisa, não o que o provider passou a devolver. Quando a verificação roda contra esse contrato inalterado, a incompatibilidade aparece na hora, e o build fica vermelho antes de qualquer coisa ser entregue.

É esse o objetivo: o contrato funciona como uma rede de segurança que pega breaking changes no CI, em vez de deixar os consumers descobrirem em produção.

Comandos

# Instalar dependências
npm install

# Iniciar API provider (Express na porta 4000)
npx ts-node provider/src/index.ts

# Executar testes do consumer (gera o arquivo pact)
npm run test:consumer

# Executar verificação do provider (valida contra o pact)
npm run test:provider

# Executar ambos
npm run test

GitHub Actions

O projeto inclui um workflow de CI (.github/workflows/contract_tests.yml) que roda em push e pull requests para main:

  • Configura Node.js 22 com cache do npm
  • Instala dependências com npm ci
  • Executa testes do consumer para gerar arquivos Pact
  • Executa verificação do provider contra os arquivos Pact gerados

Rotas do provider

Método Rota Descrição
GET /users Listar todos os usuários
GET /users/:id Buscar usuário por ID
POST /users Criar usuário (valida email, unicidade)

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages