Contract Testing — Consumer-Driven Contracts vs Schema Contracts
Consumer-driven (Pact) vs provider schema (OpenAPI/AsyncAPI/Protobuf) vs shared-env e2e. Contracts catch breaking API changes in CI without staging.
- 1Gist
- 2Maps
- 3Q&A
- 4Sandbox
Voice readout needs Web Speech Synthesis in this browser.
Two services you own might break each other
Prefer
Contract in CI
The consumer records the interaction it needs. The provider verifies that interaction, or a schema, before either side deploys.
- Feedback is a unit-scale provider test, not a staging cluster.
- Pact catches the examples consumers actually send.
- A schema scales when many consumers share one provider contract.
Alternative
Shared staging as the only check
Deploy everything, then click through. You will find some breaks, late, and mixed with flakes.
- The suite waits on other teams' deploys.
- A red run might be data, not a schema break.
- Happy-path staging still misses a field only one consumer reads.
Pact, as a straight line
The source sequence is consumer, broker, provider. Lifelines collapse into one column so the card does not clip them.
- 1
Consumer CI
The consumer test drives its client against a mock provider and records the interaction. - 2
Broker
The pact is published. The provider CI fetches the contracts it must satisfy. - 3
Provider verify
The provider replays those requests against its app and checks the response. - 4
Can-i-deploy
The consumer asks the broker whether the version it wants is verified. Deploy only on yes.
Overview
Microservices fail at boundaries. A full staging end-to-end run is slow and flaky, and it still might not execute the one field a single consumer depends on. Contract tests encode the agreement between consumer and provider. That agreement is either consumer-driven, usually Pact, or a schema the provider publishes, OpenAPI, AsyncAPI, or Protobuf. Both are verified in CI.
This is not change data capture. If you are tailing a write-ahead log, you are in the data-engineering cluster. If you are promising that GET /users/{id} still returns email, you are here.
URI style, status codes, and wire compatibility are taught elsewhere. Use those pages when the question is how to design the contract. Use this page when the question is how to test that the contract still holds.
Three ways to learn that a boundary broke
| Approach | Who writes the truth | Strength | Weakness |
|---|---|---|---|
| Consumer-driven (Pact) | Consumers | Exact interactions | Broker to operate |
| Schema contract | Provider or an IDL | Scales to many consumers | Can miss semantics |
| Staging end-to-end | Nobody typed it | Can catch infra | Slow and flaky |
Consumer-driven contracts. Each consumer writes tests that describe the requests it makes and the response fields it needs. Those tests run against a Pact mock. The generated pact is published to a broker. The provider's CI downloads the pacts and verifies them against the real application. The consumer calls can-i-deploy before it ships, so it does not deploy against a provider that has not verified that pact.
Schema contracts. The provider, or a shared IDL, publishes OpenAPI, AsyncAPI, or Protobuf. Consumers generate clients or validate payloads against that schema. This scales when dozens of consumers share one document. It can miss semantics: a field is present and a string, and the consumer still breaks because the date format changed. Match on types and presence. Do not pin exact timestamps in a Pact matcher, or every clock tick becomes a failure.
Staging end-to-end. Useful when the risk is the mesh, IAM, a Kafka ACL, or real performance. Useless as the only proof that a JSON shape survived. Keep a thin journey suite from the pyramid page. Do not make it the contract suite.
Flow
- 1
1. Consumer writes examples
- next2. Publish pact to broker
- 2
2. Publish pact to broker
- next3. Provider CI fetches pacts
- 3
3. Provider CI fetches pacts
- next4. Provider verifies its app
- 4
4. Provider verifies its app
- next5. Consumer asks can-i-deploy
- 5
5. Consumer asks can-i-deploy
- next6. Deploy only on broker yes
- 6
6. Deploy only on broker yes
Lesson map
Contract Testing — Consumer-Driven Contracts vs Schema Contracts
Consumer-driven (Pact) vs provider schema (OpenAPI/AsyncAPI/Protobuf) vs shared-env e2e. Contracts catch breaking API changes in CI without staging.
Architecture. Architecture
Select a node to see why it exists, or an edge to see the protocol, direction, effect, and consequence.
Mermaid export
flowchart TB a["1. Consumer writes examples"] b["2. Publish pact to broker"] c["3. Provider CI fetches pacts"] d["4. Provider verifies its app"] a -->|1. Consumer writes examples| b b -->|2. Publish pact to broker| c c -->|3. Provider CI fetches pacts| d
Integration tests inside one service are a different layer. They prove your handler, your database, and your middleware. A contract proves the boundary. Replacing integration with Pact leaves SQL untested. Replacing Pact with in-process integration leaves the other team's deploy untested.
When a contract beats staging
Use a contract when the risk is request, response, or event shape. Keep a thin end-to-end set for journeys and for infrastructure you cannot encode in a pact. Version breaking changes. Ship /v2 beside /v1, or an equivalent additive Protobuf change, until consumers migrate, and let can-i-deploy block a consumer that still needs the old shape. Field numbering and reserved tags are the Protobuf page, not this one.
Async events follow the same split. A schema registry plus the consumer's expected fields is the contract. A full multi-service staging run is how you learn the ACL was wrong. Run that as a smoke, not on every pull request.
Cultural red flag: a pact or an OpenAPI file that the provider CI never verifies. The document rots, consumers trust it, and the first failure is production.
A schema check you can run here
The sandboxes are not a Pact broker. They show the assertion a provider test makes: required keys exist. A real Pact test would also record the path, the method, and matchers. A real OpenAPI check would use the published document. The rule is the same.
ProblemThe provider test checks the keys this consumer reads. Extra fields are fine. Missing required keys are not.
ExpectedA complete user body passes. A body that drops email or roles fails. A list is not an object.
Edge cases
- A list is not an object.
- Extra keys stay allowed.
- Test: required keys present
schema_ok({'id': 'u-1', 'email': 'a@ex.com', 'roles': ['reader']}, {'required': ['id', 'email', 'roles']}) - Test: extra keys allowed
schema_ok({'id': 'u-1', 'email': 'a@ex.com', 'roles': ['reader'], 'extra': True}, {'required': ['id', 'email', 'roles']}) - Hidden: missing keys fail
- Hidden: list is not an object
Press Run. Snippets must be self-contained — no network, files, or native modules.
Press Run. Snippets must be self-contained — no network, files, or native modules.
Interview Q&A
What is a consumer-driven contract?
Answer
Consumers declare the interactions they depend on. Those interactions are published, often through a Pact broker. The provider verifies them in its own CI, against its real app, before either side is allowed to deploy. The consumer does not get to invent a private copy of the provider and hope production matches.
How is that different from an OpenAPI document?
Answer
A schema contract is authored by the provider or by a shared IDL. It describes the whole surface and scales to many consumers. Consumer-driven tests capture concrete examples of what each consumer actually calls, including the fields it reads. Schemas miss some semantic drift. Pact misses the calls nobody wrote a consumer test for. Mature teams often keep both: schema for the catalog, Pact for the interactions that hurt when they break.
Do contracts replace integration tests?
Answer
No. Contracts sit on the boundary between deployables. Integration tests sit inside one process: handler, database, middleware order. You need both. A green Pact file does not prove your SQL. A green repository test does not prove the other service still sends roles.
When do you still want staging?
Answer
When the failure is environmental. Service mesh policy, IAM, a Kafka ACL, or a performance ceiling will not show up in a pact replay. Staging, or a thin end-to-end smoke, covers that class. It is a bad sole owner of request and response shape.
How do you version a breaking change?
Answer
Run two versions until consumers migrate. That may be /v2 beside /v1, or an additive schema change that old readers ignore. Can-i-deploy stays red for a consumer whose pact the new provider fails. Delete the old version only after the broker shows every consumer has moved. Designing the URL or the field number is the API and Protobuf pages.
What is a practical Pact matcher rule?
Answer
Match types and presence, not exact timestamps, UUIDs, or other values the provider generates. An exact timestamp matcher turns a correct response into a red build every run. Over-specified matchers are how contract suites become flaky.
How do async events fit?
Answer
Treat the event schema as the contract. A schema registry holds the document. Each consumer states the fields it reads. The producer verifies it still emits those fields. A full bus in staging is for ACLs and lag, not for "is userId still a string."
What cultural red flag do you listen for?
Answer
Contracts that are written once and never verified on the provider CI. An OpenAPI file in a wiki, or a pact nobody fetches, is documentation with no teeth. If the provider can merge a breaking response without a red build, you do not have contract tests.
Why not call this change data capture?
Answer
The acronym CDC is already taken on this site for the log-tailing pipeline. Consumer-driven contracts do not read a database log. They record an expected interaction and replay it. Mixing the two in an interview answer signals you grabbed the acronym and skipped the mechanism.
What do you say in the first minute?
Answer
Pact is consumer-driven: consumers publish interactions, providers verify, can-i-deploy gates the release. OpenAPI, AsyncAPI, and Protobuf are provider or IDL schemas and scale further, with less semantic bite. Staging still catches mesh and IAM. It is the wrong only net for a breaking field.
Pitfalls
- Pinning exact timestamps in matchers and calling the suite flaky.
- Skipping provider verification because the schema "is in git."
- Replacing repository integration tests with a pact.
- Using staging end-to-end as the only boundary check.
- Teaching URI design or Protobuf wire types in the contract test review. Link those pages instead.
- Confusing this CDC with change data capture.
A provider wants to rename email to emailAddress. List what a Pact consumer test fails on, what an OpenAPI diff fails on, and what a staging click-through might miss if that consumer is not in the journey. Say which check blocks the provider merge.