skip to content

Contract Testing

Proving two services speak the same language without running the whole system: consumer-driven contracts, a broker workflow and compatibility gates in CI. Microservice interviews probe it directly.

on this pageshow

explore

questions

29

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

In Pact, what does an asynchronous message pact verify, and what does excluding the transport leave unproven?

level: middleimportance: must knowfreq 62%

basics

~20 s

An asynchronous Pact message pact verifies that a producer emits a payload the named consumer's handler can decode. It covers payload shape and message metadata only. Topic, broker, delivery and serialization on the wire stay unproven.

open as a page

What does the Pact Broker store that a shared folder of pact files cannot?

level: middleimportance: must knowfreq 64%

basics

~20 s

The Pact Broker stores pacts as a relation: each pact belongs to a named application and a specific version, and each verification result is recorded against that pact and the provider version that ran it. A folder stores only bytes.

open as a page

What does a Pact consumer test do at runtime, and when does it write the pact file?

level: middleimportance: must knowfreq 74%

basics

~20 s

A Pact consumer test starts a local mock HTTP server, registers the expected interactions on it, and runs the real client code against that server. The pact JSON file is written only when every registered interaction was requested and matched.

open as a page

How does a Pact provider verification run fetch pacts and replay each interaction?

level: middleimportance: must knowfreq 72%

basics

~20 s

A Pact verification run loads pact files from a broker, a URL or a local folder, then per interaction applies the provider state, sends the recorded request to the running provider, and matches the real response against the recorded expectations.

open as a page

What does an OpenAPI spec differ such as oasdiff actually compare, and what verdicts can it report?

level: middleimportance: must knowfreq 62%

basics

~20 s

A spec differ compares two revisions of one machine-readable API description - a baseline OpenAPI document against the candidate - and classifies each difference as breaking, non-breaking or informational. It reads documents only; it never calls a running service.

open as a page

In Spring Cloud Contract, what does the producer's build generate from a contract file, and when does it fail?

level: middleimportance: must knowfreq 66%

basics

~20 s

Spring Cloud Contract's build plugin reads Groovy or YAML contract files on the producer, generates one JUnit test per contract that exercises the real controller, and fails the producer's build when the service's actual response does not match the contract.

open as a page

What does the Pact Broker's `can-i-deploy` actually ask, and what must already be recorded for it to answer?

level: seniorimportance: must knowfreq 68%

basics

~20 s

It asks whether one application version is compatible with the versions of its counterparts currently in a named environment, by looking for a successful verification result for every relevant pact. Missing results count as unknown, and unknown fails the check.

open as a page

How does Spring Cloud Contract's producer-first workflow differ from Pact's consumer-driven one in who must act?

level: seniorimportance: must knowfreq 58%

basics

~20 s

Spring Cloud Contract puts the contract in the producer's repository, so the producer's own build fails first. Pact records the contract from the consumer's tests, so the provider's verification build fails and the provider must chase the consumer.

open as a page

How is the consumer side of a Pact message pact written when there is no request and no response?

level: middleimportance: should knowfreq 48%

basics

~20 s

The consumer's message handler replaces the HTTP client as the unit under test. The test declares the expected message with a builder, receives the recorded contents, and passes them to the real handler. The pact is written only if that passes.

open as a page

In the Pact Broker, what is the difference between a branch, a tag and an environment?

level: middleimportance: should knowfreq 47%

basics

~20 s

A branch is a property set on an application version when its pact is published, mirroring the git branch. A tag is a movable label on a version. An environment is a first-class object you record deployments and releases against.

open as a page

Why do literal values over-specify a Pact consumer expectation, and what does a type matcher change?

level: middleimportance: should knowfreq 68%

basics

~20 s

Where no matching rule covers a path, Pact compares by equality, so every literal you record becomes a value the provider must return forever. A type matcher replaces that with 'present, and of this JSON type' at that path.

open as a page

What does a Spring Cloud Contract stub JAR contain, and how does Stub Runner use it?

level: middleimportance: should knowfreq 54%

basics

~20 s

A Spring Cloud Contract stub JAR is an ordinary Maven artifact published with the stubs classifier, holding WireMock JSON mappings generated from the producer's contract files. Stub Runner resolves it by coordinates and serves those mappings on a local port.

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

During Pact message provider verification, how is the named message produced, and why is no real destination involved?

level: seniorimportance: should knowfreq 44%

basics

~20 s

The verifier calls a provider-side method registered under the interaction's description, which builds and returns the payload the service would emit, and compares it with the recorded matchers. Nothing is published, so no broker or topic is needed.

open as a page

Your Pact consumer expectation lists every field the provider returns. Why is that a problem?

level: seniorimportance: should knowfreq 55%

basics

~20 s

Every field in the expected body becomes an obligation checked during provider verification, so listing fields the client never reads turns the pact into a schema snapshot that fails on changes which cannot affect the consumer. Fields left out are ignored.

open as a page

What does a Pact provider record when it publishes verification results, and what breaks if it never does?

level: seniorimportance: should knowfreq 54%

basics

~20 s

Publishing records a pass or fail for each pact against the exact provider application version that ran it, along with its branch. Without it the broker has no verification record, so deploy-safety questions cannot be answered and pending pacts never become blocking.

open as a page

How does Pact-JVM bind a pact's provider-state name to a @State method, and what breaks when two handlers seed the same record?

level: seniorimportance: should knowfreq 56%

basics

~20 s

Pact-JVM matches the provider-state string recorded in the interaction against the value on a @State-annotated method by exact text, and that method seeds real provider data. Two handlers writing the same fixture rows collide, so results depend on interaction order.

open as a page

An OpenAPI diff gate reports no breaking changes for a settlement API release. What can that check still not see?

level: seniorimportance: should knowfreq 51%

basics

~20 s

A clean spec diff proves only that the description did not shrink. It cannot see a field whose shape stayed identical while its meaning changed, behaviour clients rely on that the document never described, or whether the deployed service matches it.

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

How would you run one Pact Broker across many teams without `can-i-deploy` becoming a rubber stamp?

level: principalimportance: should knowfreq 41%

basics

~20 s

Make three things non-negotiable: publish a pact per build under the commit sha, record every deployment from the automation that performs it, and fail the deploy on the broker's answer. Everything else is opt-in, and drift is measured, not assumed.

open as a page

For a given API, how do you choose between a Pact verification gate and a spec-diff gate, and when do you run both?

level: principalimportance: should knowfreq 40%

basics

~20 s

Choose by the cooperation you can obtain. A spec-diff gate needs only two revisions of the description and no other team. A Pact gate needs consumers publishing contracts and a provider build verifying them, and it names who breaks.

open as a page

In a generated Pact file, what does one interaction contain and where do its matchingRules live?

level: middleimportance: nice to knowfreq 26%

basics

~20 s

An interaction holds a description, provider states, a request block and a response block. Matching rules live inside that interaction's own request and response objects, keyed by category such as body or header and then by JSON path.

open as a page

In Pact, what does a pending pact change about the provider's build, and how do WIP pacts go further?

level: middleimportance: nice to knowfreq 27%

basics

~20 s

A pending pact is one this provider has never successfully verified, so its failures are reported without failing the provider build. WIP pacts go further: they pull in consumer pacts the provider would not otherwise fetch, and are pending by definition.

open as a page

As CI steps, what do an OpenAPI differ and buf breaking have in common, and what does the build get back?

level: middleimportance: nice to knowfreq 28%

basics

~20 s

Every spec-diff check shares one shape: obtain a baseline revision, compare the candidate against it, classify each difference by a rule set, and turn that into an exit status. The build gets typed change entries plus pass or fail.

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

How does a Pact message contract differ from a schema-registry compatibility check on the same event?

level: seniorimportance: nice to knowfreq 26%

basics

~20 s

A registry compatibility check compares schema versions structurally and represents no particular reader. A message pact compares the payload a producer actually emits against one named consumer's executed handler, so it catches semantic breaks the schema still permits.

open as a page

How does a Pact Broker webhook trigger a provider's verification build, and what does that cost?

level: seniorimportance: nice to knowfreq 27%

basics

~20 s

A webhook subscribes to broker events such as contract content changing, and posts a request to the provider's CI to start a verification build for that pact URL. It buys fast feedback and costs provider build capacity and cross-team coupling.

open as a page

In a Spring Cloud Contract DSL file, which parts drive the generated test and which drive the stub?

level: seniorimportance: nice to knowfreq 29%

basics

~20 s

One Spring Cloud Contract file compiles into two artefacts. Values wrapped in consumer(...) go into the WireMock stub; values wrapped in producer(...) go into the generated provider test. That lets the stub match loosely while the test asserts precisely.

open as a page