skip to content

Contract Testing Fundamentals

What a contract test actually asserts, how consumer-driven, producer-first and bi-directional contracts differ, and where they sit against integration and E2E tests. Interviewers open here.

on this pageshow

explore

questions

4

In Pact, Spring Cloud Contract and bi-directional contract testing, who authors the contract artefact first, and who must act when verification fails?

level: middleimportance: must knowfreq 68%

answer

  1. Ask who writes the file first
  2. Then ask whose build goes red
  3. Pact: consumer records, provider replays
  4. Spring Cloud Contract: provider declares, ships stubs
  5. Bi-directional compares documents, runs nothing

basics

~20 s

Pact is consumer-driven: the consumer records a pact file and the provider must satisfy it. Spring Cloud Contract is producer-first: the provider authors the contract and ships stubs. Bi-directional cross-checks a provider specification against consumer-recorded expectations, coupling the teams least.

solid answer

~40 s

The three workflows differ in **who writes the artefact first** and **whose build turns red**. - **Consumer-driven (Pact).** The consumer's test runs against Pact's local mock provider and emits a pact file naming both parties. The provider fetches it and replays every interaction, so a mismatch fails the *provider's* build over an expectation it never wrote. - **Producer-first (Spring Cloud Contract).** The provider authors contracts in its own repository; the build plugin generates provider verification tests and publishes a stub jar that consumers test against. Nobody surprises the provider, and consumers cannot block it. - **Bi-directional.** Each side publishes independently — a specification from the provider, recorded expectations from the consumer — and a broker cross-checks the two documents. Cheapest to adopt, weakest guarantee: it compares declarations and never executes the provider.

code

json · 8 lines
json
{
  "consumer": { "name": "berth-booking-web" },
  "provider": { "name": "berth-allocation-service" },
  "interactions": [],
  "metadata": {
    "pactSpecification": { "version": "3.0.0" }
  }
}

go deeper

for a junior

Be ready to say that a contract test checks two services agree on the messages they exchange, and that Pact records that agreement in a file which is later checked against the real provider.

for a middle

An interviewer expects you to name who authors the artefact first in each workflow and whose pipeline fails: consumer records and provider replays for Pact, provider declares and ships stubs for Spring Cloud Contract, document cross-check for bi-directional.

for a senior

Be ready to justify a choice for two specific teams: how much coupling each workflow imposes, what happens when a provider team ignores a red verification, and what the weaker bi-directional guarantee actually costs you.

for a principal

Own the estate-level call: which workflow you standardise on, who funds and operates the broker, and how you stop two workflows running on the same interface with two competing verdicts.

## Three workflows, three obligations A contract-testing workflow is settled by three organisational facts, not by cleverness: **who authors the artefact first**, **where that artefact lives**, and **whose build turns red when the two sides disagree**. Pact, Spring Cloud Contract and bi-directional contract testing answer those three questions differently, and every other difference follows from the answers. | Workflow | Artefact written by | Published to | Red build lands on | Guarantee bought | | --- | --- | --- | --- | --- | | Consumer-driven (Pact) | The consumer, as a by-product of its own test | A Pact Broker | The provider's verification job | The running provider satisfies expectations a real consumer holds | | Producer-first (Spring Cloud Contract) | The provider, by hand | The provider's repo, plus a stub jar in an artifact repository | The provider's generated tests, and any consumer whose stub run drifts | The provider does what it declared, and consumers build against that declaration | | Bi-directional | Both sides, independently | A broker that cross-checks the two documents | Whichever side published the incompatible document | Two documents agree on paper | ## Consumer-driven: the consumer writes the obligation In Pact the consumer writes an ordinary test whose HTTP calls go to Pact's **local mock provider**. The mock answers with the response the test declared, the test asserts the consumer's own client code handled it, and the run emits a **pact file**: a JSON document naming a `consumer` and a `provider` and listing the interactions that were exercised. The consumer publishes that file to a **Pact Broker**. The provider later fetches the pacts written for it and replays each interaction against the real service, publishing the results back. The consequence is organisational rather than technical: **a mismatch fails the provider's build, over an expectation the provider's team never wrote.** That is the entire point of the workflow, because it turns one consumer's real needs into a blocking concern for the provider. It is also its cost, because it only functions where the provider team accepts that obligation. Two properties follow. First, a pact protects exactly what some consumer exercised; a response field no consumer test touches is not covered by anything. Second, the consumer cannot unilaterally impose new behaviour: a newly recorded expectation is a request until the provider agrees to satisfy it. The artefact carries a conversation, it does not replace one. ## Producer-first: the provider declares and ships stubs Spring Cloud Contract reverses the direction. The provider authors contracts — Groovy DSL or YAML files — inside its own repository. Its build plugin does two things with them. It **generates provider-side verification tests** that fail if the service stops matching its own declaration, and it packages the same contracts into a **stub jar** published to an artifact repository. Consumers pull that stub jar and run their tests against it using Stub Runner, instead of against a hand-written mock they maintain themselves. The obligation is reversed with the direction. The provider is never surprised by an expectation, because it wrote every one of them; consumers, in exchange, lose the ability to state a need in a form that blocks the provider's release. What they gain is a stub guaranteed to behave like the provider, because both are generated from the same file. This workflow suits an organisation where the provider is the design authority for its API and the consumers are numerous, downstream, or simply not going to write tests for someone else's service. ## Bi-directional: cross-checking two documents Bi-directional contract testing keeps both sides independent. The provider publishes a specification of its API, each consumer publishes its recorded expectations, and a broker supporting this mode cross-checks whether every consumer expectation could be satisfied by the provider's specification. Nothing is replayed against a running service. That buys the lowest adoption cost — neither team adds a job to the other's pipeline, and neither has to wait for the other — and, in exact proportion, the weakest guarantee. The comparison is between a declaration and an expectation, so the verdict inherits the accuracy of the provider's specification. If that document has drifted from the deployed code, the cross-check will approve a pair that cannot actually talk. Consumer-driven verification catches exactly that drift, because it executes the provider. ## Choosing between them 1. **Can every consumer be asked to write and maintain a test?** If yes, consumer-driven produces the strongest evidence available. 2. **Will the provider's pipeline treat a verification failure as blocking?** If not, consumer-driven degrades into a wish list, and producer-first or bi-directional is the more honest choice. 3. **Is the provider the design authority, with many or unknown consumers?** Producer-first scales better: one declaration, stubs for everybody. 4. **Do you need behavioural evidence for this interface at all?** Bi-directional is a deliberate trade for a low-churn edge, not a lesser tool. The failure worth avoiding is running two workflows on one interface without deciding which verdict wins. A stub-jar consumer and a recorded pact on the same endpoint give two sources of truth, two maintenance costs, and two unrelated ways for a release to go red.

  • Why does a consumer-driven Pact mismatch fail the provider's build rather than the consumer's?
    The consumer's test only ever talks to Pact's local mock provider, which replays the consumer's own expectations and therefore always passes. The only run that touches the real service is the provider's verification job, which replays the recorded interactions against it. That is deliberate: the workflow exists to make one consumer's needs a blocking concern for the team that can break them.
  • What does a bi-directional cross-check miss that a replayed pact catches?
    Provider drift. A bi-directional verdict compares the provider's published specification with the consumer's recorded expectations, so it is only as accurate as that specification. If the deployed service no longer matches its own document, the cross-check still passes. Consumer-driven verification executes the provider and compares real responses, so it fails on exactly that divergence.
  • In the consumer-driven workflow, what stops a consumer recording an expectation the provider never agreed to?
    Nothing technical, and that is the point of understanding it. A newly recorded expectation is a request: publishing it does not make the provider implement it, it makes the disagreement visible in the provider's pipeline. The obligation to act is an organisational agreement between the two teams; the tooling only enforces the agreement they already made.

Consumer-driven is an order ticket the kitchen must cook to; producer-first is a printed menu the customer orders from; bi-directional is holding the ticket and the menu side by side and checking they could match.

saying these in an interview costs you the question

  • Says the provider writes the pact file in Pact
  • Calls a pact file an OpenAPI document in another format
  • Claims bi-directional gives the same guarantee as verification
  • Thinks the consumer's build fails when the provider breaks it
  • Calls Spring Cloud Contract consumer-driven because consumers use its stubs
open as a page

Once a Pact between two services verifies green, which end-to-end tests can you delete and which does it provably not cover?

level: seniorimportance: should knowfreq 58%

basics

~20 s

A verified pact retires end-to-end tests that existed only to check one consumer-provider message shape: fields, status codes, headers. It cannot cover deployment topology, data migrations, multi-service business flows or performance, which still need a real environment.

open as a page

When is consumer-driven contract testing with Pact and a shared Pact Broker not worth adopting, and what do you do instead?

level: principalimportance: should knowfreq 44%

basics

~20 s

Skip it when consumers are unknown or external, when a provider team will not treat a red verification as blocking, or when nobody will operate the broker. Publish a provider specification and gate on a compatibility diff instead.

open as a page

A Pact file and an OpenAPI compatibility check both pass for the same endpoint - what does each one still let through?

level: seniorimportance: nice to knowfreq 31%

basics

~20 s

A pact only covers interactions a consumer recorded, so it misses untested fields, consumers with no test, and meaning changes behind an identical shape. A specification diff never runs the service, missing drift and additive changes that break strict consumers.

open as a page