Skip to content

API Contract Testing with Pact — Consumer-Driven Contracts and Provider Verification

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about API Contract Testing with Pact. We cover key concepts, practical examples, and best practices to help you master this topic.

Pact is a consumer-driven contract testing framework that allows API consumers to define expected interactions and providers to verify they meet those expectations without end-to-end testing.

What You'll Learn

  • How to write Pact consumer tests for API clients
  • Setting up a Pact Broker for contract sharing
  • Running provider verification tests

Why It Matters

Integration and E2E tests are slow and brittle. Contract tests give fast feedback on whether API changes break consumers, enabling safe continuous delivery.

Real-World Use

A microservices architecture with 15 services uses Pact contracts to prevent breaking changes. When the product service updates its API, Pact provider verification catches that the change breaks the order service before deployment reaches production.

flowchart LR
    A[Consumer Test] --> B[Generates Pact]
    B --> C[Pact Broker]
    C --> D[Provider Verification]
    D --> E[Pass/Fail]
    E --> F[Deploy Decision]

Writing a Consumer Pact Test

Define the expected interaction between the consumer and provider.

import atexit
from pact import Consumer, Provider

pact = Consumer("OrderService").has_pact_with(
    Provider("ProductService"),
    pact_dir="./pacts"
)
pact.start_service()
atexit.register(pact.stop_service)

# Define expected interaction
expected = {
    "id": 1,
    "name": "Laptop",
    "price": 999.99
}

(pact
 .given("product with ID 1 exists")
 .upon_receiving("a request for product 1")
 .with_request("GET", "/products/1")
 .will_respond_with(200, body=expected))

# Execute consumer code
with pact:
    result = call_product_service(1)
    assert result == expected

Expected output: Pact generates a JSON contract file in ./pacts/order_service-product_service.json.

Provider Verification

The provider tests against the published Pact contract.

from pact import Verifier

verifier = Verifier(provider="ProductService")
verifier.verify_pacts(
    "./pacts/order_service-product_service.json",
    provider_base_url="http://localhost:8000",
)

# Verify all interactions pass
output = verifier.verify()
assert output == 0

Expected output: All consumer expectations match the provider responses.

Publishing to Pact Broker

Share contracts through a central Pact Broker for team collaboration.

# Publish pact to broker
pact-broker publish ./pacts/order_service-product_service.json \
  --broker-base-url https://pact-broker.example.com \
  --consumer-app-version 1.0.0 \
  --tag prod

Expected output: Pact uploaded and available in the broker UI.

Common Mistakes

Mistake Why It's Wrong
Testing more than the contract Pact tests should verify only the interaction, not business logic
Including dynamic values Fields like timestamps and UUIDs should use matchers
Not versioning contracts Without versions, you cannot track which consumer versions work
Skipping provider verification A contract without verification is just documentation
Using Pact for non-HTTP protocols Pact only supports HTTP interactions
Hardcoding provider URLs Use environment variables for provider base URLs
Ignoring pact tags Tags help control which contracts apply to which environments

Practice Questions

  1. What is a consumer-driven contract? A: A test written by the API consumer that defines expected request/response pairs.
  2. How does Pact differ from schema validation? A: Pact validates that the provider's actual responses match what consumers expect, not just the schema shape.
  3. What is a Pact Broker? A: A repository for sharing contracts and verification results between consumer and provider teams.
  4. How do Pact matchers handle dynamic data? A: Matchers like like(), term(), and each_like() define patterns instead of exact values.
  5. What is the can-i-deploy tool? A: A Pact CLI tool that checks if a provider version is compatible with consumers before deployment.

Challenge

Create a Pact contract for a user API consumer that expects: GET /users/1 returns status 200 with {id: 1, name: string, email: string}, POST /users with body {name, email} returns status 201, and GET /users/999 returns status 404. Run provider verification against a mock server.

FAQ

What is the difference between Pact and Postman?

Postman tests current behavior; Pact defines expected behavior and verifies it at build time.

Can Pact test Graphql APIs?

Yes, Pact supports GraphQL with custom matchers for query and response validation.

How often should contracts be verified?

Every time either the consumer or provider changes. CI/CD pipelines should run verification on both sides.

What happens when a contract fails?

The provider must either fix the API or coordinate with the consumer team to update the contract.

Does Pact support Message Queues?

Yes, Pact supports asynchronous message interactions through Pact message pacts.

How do you handle optional fields in Pact?

Use matchers with each_like or term to make fields optional in the contract.

What is a pact tag?

A label (like "prod" or "test") attached to a pact version that controls which consumers are checked during verification.

Mini Project

Implement Pact contract testing for an e-commerce system. Create a consumer (OrderService) that expects: GET /inventory/5 returns {id: 5, sku: "LAP-001", stock: 42}. Write provider verification for InventoryService that runs on every build. Publish the pact to a local broker and use can-i-deploy before deployment.

What's Next

Next, learn API mocking with WireMock to simulate dependencies during testing.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro