74

Contract testing: verify integration without an integration environment

#
  • practice
  • context-dependent
  • fragment

Conflicts with: Contract testing ↔ provider velocity

An E2E environment with every service is the most expensive and least stable way to check compatibility. Consumer-driven contracts: the consumer publishes its expectations (which fields it reads, which statuses it handles), and the provider verifies them in its own CI. A breaking change is caught before deploy, on the provider side, with the name of the affected consumer. This operationalizes Hyrum’s Law (16): the contract is what consumers actually use, recorded in machine-readable form.

The pattern was described by Ian Robinson in “Consumer-Driven Contracts: A Service Evolution Pattern” (martinfowler.com, 2006): a provider’s obligations are the union of what its consumers actually rely on, and making those expectations explicit lets the provider evolve everything else freely. Pact, started at realestate.com.au, turned it into tooling: consumer tests run against a mock that records the interactions as a pact file, a broker stores the pacts per version, and the provider replays them against its real implementation. The broker’s can-i-deploy check answers the release question directly: is this provider version verified against every consumer version in production?

Code example (fragment)
Python · pact-stylefragment
# consumer expectation
pact.given("invoice 42 exists") \
    .upon_receiving("get invoice") \
    .with_request("GET", "/invoices/42") \
    .will_respond_with(200, body=Like({"number": "42", "total_cents": 12300}))

# the provider runs ALL published consumer contracts in its own CI;
# removing a field someone reads = a red build for the provider

Contracts check structure, not behavior — they complement the provider’s tests rather than replace them. A schema registry (protobuf + compatibility checks, 18) does the same for events; contract testing covers boundaries that have no registry (REST/JSON).

Where contracts fail. They protect only consumers who publish one; a consumer that never wrote a pact is invisible, which is Hyrum’s Law again. A public API with unknown clients cannot be contract-tested at all — there, versioning and an OpenAPI diff in CI do the job. Over-specified contracts are the common misuse: a consumer that asserts exact values instead of types and shapes (hence Like above) makes every provider change red, and teams learn to ignore the build.

Last technical review: 2026-08-04 · Reference · 112 principles