API Contract Testing with Pact — Consumer-Driven Contracts and Provider Verification
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
- What is a consumer-driven contract? A: A test written by the API consumer that defines expected request/response pairs.
- 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.
- What is a Pact Broker? A: A repository for sharing contracts and verification results between consumer and provider teams.
- How do Pact matchers handle dynamic data?
A: Matchers like
like(),term(), andeach_like()define patterns instead of exact values. - 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