Skip to main content

Contract and Consumer-Driven Tests

Test API contracts to catch breaking changes before deployment.

TL;DR

Contract testing verifies that an API meets the expectations of its consumers. Consumer-driven contracts (CDC) start from the consumer's needs, not the provider's implementation. A consumer writes tests defining what data/behavior it needs; the provider verifies it delivers that. Catch breaking changes before deployment. Use tools like Pact to record and replay contracts. Contract testing is not integration testing—it mocks the provider, making tests fast and isolated. Perfect for microservices where many teams depend on shared APIs.

Learning Objectives

  • Design contracts between services
  • Write consumer-driven contract tests
  • Use Pact or similar tools
  • Integrate contract testing into CI/CD
  • Catch breaking changes early
  • Version APIs based on contract changes

Motivating Scenario

Service A calls Service B's /products endpoint, expecting fields id, name, price. Service B removes price field without telling Service A. Service A breaks. With contract testing, Service B's change fails CI before it's deployed.

Core Concepts

AspectContract TestingIntegration Testing
Mock provider?YesNo
SpeedFast (mock)Slow (real service)
ScopeAPI contract onlyFull end-to-end flow
Runs in CI?Every commitOnce per day
Detects breaking changes?YesYes, but late

Contract Testing:

  • Consumer mocks provider
  • Consumer writes test: "I expect /products to return {id, name, price}"
  • Provider runs same test against real implementation
  • Fast, isolated, catches breaks early

Integration Testing:

  • Start both services (real or containers)
  • Consumer calls provider (real)
  • Tests full workflow
  • Slow, catches integration bugs, not API contract breaks

Best practice: Both. Contract tests catch API breaks. Integration tests catch real integration issues.

When to Use / When NOT to Use

Contract Testing: Best Practices
Best Practices
  1. DO: Contract Test Service Boundaries: Every API consumer/provider pair has contract tests. ServiceA depends on ServiceB? Contract test.
  2. DO: Consumer Writes Contract: Consumer (ServiceA) defines what it needs. Provider (ServiceB) proves it delivers.
  3. DO: Use Pact or Similar Tool: Pact records consumer expectations, provider verifies. Shareable, version-controlled contracts.
  4. DO: Integrate into CI: Every commit: consumer tests pass, provider tests pass. Can-deploy checks before merge.
  5. DO: Version APIs Based on Contracts: API breaking change? Bump version (/v2/). Old consumers stay on /v1/.
Anti-Patterns
  1. DO: Contract Test Service Boundaries: Only integration test (real services). Skip contract tests. Miss breaking changes in CI.
  2. DO: Consumer Writes Contract: Provider guesses what consumers need. Ship breaking change, discover in production.
  3. DO: Use Pact or Similar Tool: Manual contract checking. Error-prone, not automated.
  4. DO: Integrate into CI: Manual verification. Rely on humans to check contracts.
  5. DO: Version APIs Based on Contracts: Remove field without versioning. All consumers break.

Patterns & Pitfalls

ServiceB removes field, no one knows. ServiceA breaks in production. Customer angry.
Contract tests exact timestamps, IDs, emails. Brittle. Fail for unrelated changes.
Consumer writes contract. Provider verifies. Alignment guaranteed.
Central contract repository. CI checks: can ServiceB version XYZ deploy with ServiceA version ABC?
No contracts in CI. Breaking change deployed. Discovered in production. Down for hours.
Contracts catch API breaks fast. Integration tests verify full workflow. Best of both.

Design Review Checklist

  • Does every API consumer/provider pair have contract tests?
  • Are contracts written by consumers (not guessed by providers)?
  • Are contracts stored in Pact or similar tool (not manual)?
  • Are contracts version-controlled (in git)?
  • Are contract tests run in CI for every commit?
  • Can CI block incompatible deployments (can-deploy)?
  • Are contracts flexible (use matchers, not exact values)?
  • Are APIs versioned (/v1/, /v2/) to handle breaking changes?
  • Are consumers notified of API deprecations (6-month notice)?
  • Are old API versions supported for transition period?
  • Is Pact broker (or similar) set up for contract sharing?
  • Are contract tests fast (< 1 second)?
  • Do contract tests focus on API contract, not implementation?
  • Can new API versions be tested alongside old (parallel support)?

Self-Check

  1. Right now, if you remove a field from an API, does CI catch it before deploy?
  2. Do all API consumers have tests that verify provider behavior?
  3. Do contract tests run in CI for every commit?
  4. Can you deploy a breaking API change without CI failing?

Next Steps

  1. Identify API boundaries — Which services depend on which?
  2. Write consumer contracts — For each dependency, consumer writes contract
  3. Set up Pact — Store contracts, share between consumer/provider
  4. Integrate into CI — Consumer tests + provider tests for every commit
  5. Add can-deploy check — Block incompatible deployments
  6. Version APIs — Use /v1/, /v2/ for major changes
  7. Document contracts — Keep OpenAPI/AsyncAPI specs in sync

References

  1. Pact Foundation ↗️
  2. Martin Fowler: Consumer-Driven Contracts ↗️
  3. Pact: Scope of Contract Testing ↗️
  4. Google Cloud: API Versioning ↗️