Hermes Wiki
Developer/DeveloperTools/Testing/ContractTests/Fundamentals/consumer-driven-contract-testing

Consumer-Driven Contract Testing

Concept

A contract test verifies that two independently-deployed services agree on the shape of an API interaction — without spinning up either service's real dependencies, and without the slowness and flakiness of a full end-to-end test that exercises the whole system together. In the consumer-driven variant (the model popularized by the Pact framework), the consumer of an API — not the provider — writes the contract: it records, as part of its own test suite, the exact requests it will make and the responses it expects back. That recorded interaction is published as a "pact" (a contract file, historically JSON) to a shared broker. The provider then runs a separate verification step against that same pact — replaying the consumer's expected requests against its own real implementation and checking every response matches what the consumer asked for. If it doesn't, the provider's CI fails before a breaking change ships, not after a consumer discovers it in a shared staging environment or production.

The "consumer-driven" framing matters: contracts are scoped to only what a consumer actually uses, not a fully theoretical description of everything a provider's API could do. A provider can freely change or add fields no consumer has asserted on; it only breaks a contract by changing something a consumer explicitly depends on. This keeps the contract surface small and directly tied to real usage, rather than turning into a second, drifting copy of the whole API schema.

Tradeoffs

Approach Catches breaking changes Speed Setup cost Coverage of real integration behavior
Full end-to-end tests (real services, real network) Yes, comprehensively Slow — real network calls, real service startup Low to author, but environment upkeep is ongoing and often the biggest cost Highest — exercises the actual deployed stack
Mocked/stubbed integration tests (consumer tests against a hand-written stub) Only if the stub is kept in sync with the real provider by hand Fast Low Low — a stub that's silently drifted from the real API gives false confidence
Consumer-driven contract tests (Pact-style) Yes, for anything a consumer actually asserts on Fast — no real service startup, contract replay is local Medium — requires a broker, and both consumer and provider CI need a verification step Medium — precise for asserted interactions, silent on anything no consumer has contracted against
Provider-driven / schema-first contracts (e.g. OpenAPI schema diffing) Yes, for schema-level shape changes Fast Medium — requires schema authoring/maintenance discipline Medium — catches schema drift but not semantic/behavioral mismatches a consumer actually depends on

The core tradeoff against full E2E tests is precision versus completeness: contract tests are fast and pinpoint exactly which consumer/provider pair broke and how, but they only know about interactions a consumer has actually written a test against — an untested consumer expectation is an invisible gap no contract test will catch.

When to use / when not to

  • Use once there are at least two independently-deployed services that need to agree on an API shape — a single backend serving a single frontend, both deployed together, gets little value since there's no independent-deployment risk to guard against yet.
  • Especially valuable at team boundaries: when the consumer and provider are owned by different teams (or even different companies), a contract test catches a breaking provider change in the provider's own CI, before it ever reaches the consumer's environment — turning a cross-team incident into a local CI failure.
  • Pair with, not instead of, a smaller number of true E2E tests — contract tests verify the shape of an interaction, not that the whole system behaves correctly when wired together; keep a thin E2E layer for genuine full-stack confidence.
  • Skip it for a monolith or a single-team system with no independently-versioned API boundary — the coordination and broker infrastructure cost isn't justified when both sides of an interaction always deploy together and integration bugs are already caught by ordinary integration tests.

Common pitfall

Writing contracts that assert on the full shape of a response rather than only the fields the consumer actually uses. A consumer that (accidentally) asserts on every field in a response — including ones it never reads — turns an unrelated, harmless provider change (adding a new field, reordering an unused one) into a false-positive contract failure. This defeats the entire "consumer-driven" premise: the contract is supposed to describe dependency, not the full response shape, and a contract that's stricter than the consumer's actual usage produces exactly the kind of noisy, ignored-until-it's-routine failures that erode trust in the whole practice.

The other half of this pitfall is skipping provider verification in CI, or running it only manually/occasionally. A published contract that the provider never actually verifies against is worse than no contract at all — it gives the consumer team false confidence that a safety net exists, when in practice nothing is checking whether the provider still honors it.

Engineering Lens

The real judgment call isn't "contract tests vs. E2E tests" — it's recognizing that they answer different questions and belong at different points in the pipeline. A contract test answers "does this specific consumer/provider pair still agree," fast enough to run on every commit; an E2E test answers "does the system work end-to-end," slow enough that it belongs later in the pipeline and in smaller numbers. Introducing contract testing without first establishing which team owns fixing a broken contract — consumer or provider — tends to produce exactly the ambiguity contract testing was meant to eliminate; the broker and the CI wiring are the easy part, the cross-team ownership agreement is what actually makes it work.

Sources

Hermes Wiki