APIs and integrations

3 min read ·

Contract Testing: Two Green Services Can Still Break Checkout

The orders team changed a response; payments still expects the old shape. Both services pass their own tests, yet checkout fails.

The orders service releases a new version. Its tests pass. Payments is green too. Then checkout breaks: the consumer expects a status in one format while the provider has started returning it in another.

Both applications may be perfectly healthy in isolation. Their shared agreement is not. Contract testing aims to catch this kind of incompatibility before release without having to deploy the entire system and coordinate every team for a full integration test.

What a contract actually checks

An API is more than documentation and a list of fields. Consumers rely on concrete interactions: a request shape, a response status, required data and agreed error behaviour. A response may be valid according to the provider's new schema and still unusable by a client that expects the old one.

With consumer-driven contract testing, the consumer describes the minimum interactions it depends on. The provider then verifies those expectations against its implementation. There is no reason to freeze the entire response if the consumer only needs two fields. Overly strict snapshots turn harmless API extensions into red builds.

A contract test also differs from a client-side mock test alone. A mock invented by the consumer team may confirm any assumption the team happens to like. Confidence comes when the provider actually verifies those expectations.

Verification needs to happen before deployment

Suppose the orders team wants to change a field type. Before deploying, its build verifies the contracts of relevant consumers. If one application cannot interpret the new response, the mismatch becomes visible in CI rather than in a customer's failed purchase.

That requires a way to share contracts, match them to consumer versions and connect verification results to what is actually deployed. Checking two development branches once is not enough to prove that a particular production combination is safe.

The Pact provider documentation describes verification against a running provider in CI. The tool does not remove the need for agreement about API behaviour; it makes the consequences of changes visible earlier.

What green contract tests cannot prove

A passing contract test confirms specific interactions. It does not prove that a customer paid successfully, received a message and saw the correct total. Services can agree on fields while disagreeing about the actual business rule.

Authorisation, realistic data, timeouts, performance and selected end-to-end customer journeys still need their own checks. Contract tests complement those tests and can reduce the pressure to run expensive full-system checks after every minor change.

Tests that lock down accidental implementation details are especially harmful. If a client does not depend on an extra response field or on the order in which object properties appear, blocking such harmless changes buys very little safety.

Who removes the contract of a retired consumer?

When an API supports several products, consumer contracts can outlive the applications that created them. An abandoned test may keep preventing sensible changes. Contracts need owners, version awareness and a way to retire obsolete expectations.

I would start with the last actual integration failure rather than with a tool purchase. Which incompatible assumption caused it? Would a consumer contract have caught it? How often do independent teams change this API?

For an interface maintained and consumed by one small team on the same release cycle, elaborate contract-testing infrastructure may add more work than benefit. For independently deployed products with expensive compatibility failures, verified contracts can materially reduce coordination cost.

Contract tests help teams ship independently. They guarantee what was actually verified, not that the entire customer journey has become safe.