Skip to content
QSWEQB
mediumConceptDesignScenarioMidSenior

Two teams own two services that talk to each other. How do you stop one breaking the other without running both together in a shared environment?

Contract testing has each side verify itself against a shared description of the interaction, so consumer expectations are checked against the provider's real behaviour without the two ever running together. Mismatches surface in each team's own pipeline, not in a shared environment days later.

4 min readUpdated 2026-07-26

What the interviewer is scoring

  • Does the candidate explain how both sides are verified without ever running together
  • Whether they identify that a schema check alone misses semantic expectations
  • That the provider's obligation is limited to what consumers actually use
  • Whether the failure is placed in the breaking team's pipeline rather than in a shared environment
  • Does the candidate say what contract testing does not cover

Answer

The problem with the shared environment

The default answer is to deploy both services somewhere and run tests against the pair. It works, and it has three properties that get worse as the number of services grows.

It is late. The mismatch is found after both services are built and deployed, which is hours or days after the change that caused it, and by then the author has moved on.

It is shared, so it is contended and frequently broken by someone else. A red suite in a shared environment usually means a third team deployed something, and the signal degrades to noise.

And it scales badly. Testing a service against its real dependencies means those dependencies' dependencies too, and eventually the integration environment is the whole system, which nobody can keep healthy.

What a contract test does instead

The trick is that the two sides are verified separately, against a shared artefact, and never run at the same time.

The consumer writes a test against a mock provider, saying: when I send this request, I expect a response of this shape, and here is what I do with it. Running that test produces a contract — a machine-readable record of the interactions the consumer relies on.

The provider then takes that contract and replays each recorded request against the real provider, checking that the real response satisfies what the consumer expected. No consumer code is running; the contract stands in for it.

The result is that each team learns about a mismatch in their own pipeline, from a test they can run locally, at the moment they make the change. The provider that removes a field sees its build go red with a message naming the consumer that needed it, before the change is merged. That relocation of the failure — from a shared environment later to the breaking team's pipeline now — is the entire value.

Why the mock does not lie

The obvious objection is that the consumer test used a mock, and a mock is whatever you wrote it to be. That is exactly right and it is why the consumer half alone is worthless.

The verification step is what closes it. The contract is not a description of what the consumer imagines; it is a claim that gets checked against the provider's actual running code. If the consumer expected a field the provider never sends, provider verification fails and the contract is corrected. A contract that has not been verified against the provider is just a mock with paperwork.

What this gives you over a schema

Publishing an OpenAPI document and validating against it is a genuine improvement over nothing, and it stops short in two ways.

A schema describes what the provider can return. A contract describes what a consumer actually depends on. That difference is what makes evolution safe: a provider can freely change anything no consumer's contract mentions, and knows precisely which changes are breaking and for whom. With only a schema, every field is potentially load-bearing and every change is a risk, so providers become reluctant to change anything.

The second difference is semantic. A schema says status is a string. A contract can say that for this request the consumer requires status to be SETTLED, and that it reads settledAt when it is. That is a behavioural expectation, and it is the kind of thing that breaks in production while the schema still validates.

The limits, which matter

Contract testing verifies one interaction between two parties. Everything outside that is out of scope, and pretending otherwise is how teams get burned.

It says nothing about whether a multi-service workflow produces the right business outcome, nothing about performance, nothing about whether the services can actually reach each other in the deployed environment, and nothing about configuration or infrastructure. A small number of end-to-end tests still exist to answer "is the system assembled and wired correctly" — contract testing is what lets that number stay small.

It also carries real overhead. A broker to store and version contracts, a verification step in the provider's pipeline, and a discipline about which contract version is deployed where. For two services owned by one team, that overhead exceeds the benefit and a straightforward integration test is the better answer. The pattern earns its cost at the point where the two sides are owned by teams on different release schedules, which is precisely the situation where the shared environment was failing you.

The consumer's mock is only meaningful because the provider replays it against real code. Skip the verification step and you have automated your assumptions rather than tested them.

Likely follow-ups

  • A provider adds a required request field. Which pipeline goes red, and when?
  • How is this different from publishing an OpenAPI spec and validating against it?
  • What does contract testing not catch that you still need an end-to-end test for?
  • Who owns the contract, and what happens when a consumer wants a field the provider will not add?

Related questions

Further reading

contract-testingapi-testingmicroservicesconsumer-driven-contractsci