When two services talk to each other, the bug nobody catches is "the producer changed the response shape and the consumer didn't notice." Contract testing makes the wire shape between them an explicit, versioned contract — verified by both sides independently, with no need to spin up the whole environment. The right answer for microservices that need to deploy independently.
← Back to TestingThe producer publishes a schema (OpenAPI YAML, .proto, AsyncAPI). Both sides validate against it:
openapi-typescript, Buf). Compile-time errors when the consumer expects a field the schema doesn't have.Best for stable, well-governed APIs — public APIs, partner contracts, gRPC. The schema is the source of truth.
The consumer writes example interactions: "when I GET /users/42, I expect a JSON object with name and email." Pact captures these as a contract file. The producer's CI runs against the contract file and verifies its real responses satisfy the consumer's expectations.
Why CDC: the consumer's tests describe the parts they actually use. The producer can change anything else freely. Adding a field is fine; removing one a consumer relies on fails the consumer's contract on the producer's CI.
Where contracts and verification results live. PactFlow (managed) or self-hosted Pact Broker. Tracks which versions of which consumers verified against which producers — and gates deployments via can-i-deploy: "can consumer v3.4 safely deploy given the producer versions currently in production?"
Same idea for events. Pact has message-pact for queue/Kafka contracts. AsyncAPI is the schema-first equivalent of OpenAPI for events. Schema registries (Confluent Schema Registry, Apicurio, Buf Schema Registry) enforce backward/forward compatibility on every produced event.
The consumer's tests run in isolation against a stub satisfying the contract. The producer's CI verifies its responses still match. Neither needs the other running. PR pipelines stay fast; the answer "did I break my consumers?" is computed at PR time.
This is what microservices were promised to deliver. With contracts, the producer team can deploy whenever its contract is unbroken; the consumer team can deploy whenever it satisfies its expected response shape. No release trains.
OpenAPI / Protobuf schemas double as documentation. With CI verification, they can't drift out of sync with the code — the build fails before that happens.
Matchers, state setup, broker workflows, can-i-deploy. Teams that adopt Pact without budget for the ramp-up often abandon it within a quarter. Provider-state setup, in particular, requires real engineering investment.
Contracts in source files only work for tiny systems. Past 3-4 services, you need a broker — managed (PactFlow) or self-hosted — and someone owning it. The broker is now part of your platform.
Contracts verify the wire shape. They don't verify the producer's semantics — does it actually compute the right answer? That still needs the producer's own integration tests. Contracts are for compatibility, not correctness.
For public APIs with unknown consumers, you can't use CDC — there's no consumer test to consume. Use schema-first (OpenAPI / Protobuf), version explicitly, and treat the schema as a public contract.
| Setup | Approach |
|---|---|
| Multiple internal services that need to deploy independently | Pact (CDC) + a broker. |
| Public REST API with known partners | OpenAPI + Schemathesis on the producer side. |
| gRPC service mesh inside one company | Protobuf + Buf breaking-change checks. |
| Event-driven architecture | Schema registry (Avro/Protobuf) + AsyncAPI; or Pact message-pact. |
| Two services owned by the same team | Probably overkill — integration tests cover it. |