Contract Testing

When two services communicate, each makes assumptions about the other: which endpoints exist, which fields are present, which types and status codes come back. A contract test checks those assumptions directly — the consumer records what it needs, and the provider proves it still delivers it — without deploying both services into a shared environment and running a slow end-to-end suite.

Contract testing solves a specific, expensive problem in distributed systems: a provider team renames a field, all of its own tests pass, it deploys, and a consumer breaks in production. Unit tests on each side cannot catch this, because each side tests against its own idea of the interface. Contracts make the interface itself the thing under test.

TL;DR

Quick Example

A consumer test with Pact JS: the web app declares what it needs from the orders API, and its real client code runs against a Pact mock server.

Running the test produces a contract file. The orders API later replays every interaction in it against its real implementation; if it renamed status to state, verification fails in the provider's own pipeline.

Core Concepts

Consumers, Providers, and Interactions

The consumer calls an API or reads messages; the provider serves them. A contract is a set of interactions — a request and the minimal response the consumer depends on. Consumers specify only what they use, so providers stay free to add fields and change everything the consumer ignores.

Matchers

Contracts should describe shape, not exact data. Matchers such as like (same type), eachLike (array of items with this shape), and regular expressions keep contracts robust: the consumer needs a status string, not the literal value from a test fixture.

Provider States

An interaction often needs data to exist — "order 42 exists". The provider implements state handlers that set up that data before replaying the interaction, keeping each verification independent.

Provider Verification

The provider pulls contracts for itself, starts its real application (often with external dependencies stubbed), replays each interaction, and checks the responses.

The Broker and can-i-deploy

A contract broker stores contracts, provider verification results, and which versions run in which environment. Before deploying, each service asks whether its version is compatible with everything already deployed:

This turns integration compatibility into a fast, automated deployment gate.

Approaches Compared

For specification-first contracts, the risk is drift between the specification and the implementation; generating or validating the specification from the code closes that gap — see OpenAPI and API Contract Generation.

Contracts for Messages and Events

Asynchronous systems need contracts too. Message pacts describe the payloads a consumer expects to receive; schema registries reject incompatible schema changes at publish time. See Event Schema Evolution and Data Contracts and Schema Evolution.

Where Contract Tests Fit

Contract tests remove the need for most end-to-end tests whose real purpose was "do these services still fit together?", leaving a small set of end-to-end tests for critical journeys.

Best Practices

Let Consumers Specify Only What They Use

A consumer that asserts on every field of a large response couples itself to all of them. Asserting only on the fields it reads leaves the provider room to evolve.

Verify in the Provider's Pipeline

Contracts are worthless if providers do not verify them. Run verification on every provider change, publish results to the broker, and fail the build when a consumer's contract breaks.

Gate Deployments With can-i-deploy

Make the compatibility check part of every deployment, for consumers and providers alike, and record deployments in the broker so it knows what runs where.

Keep Business Logic Out of Contracts

A contract asks "can this request be served in this shape?", not "does the discount calculation work?". Test business rules with the provider's own unit and integration tests.

Talk When Contracts Break

A failing verification is the start of a conversation between teams — sometimes the provider should change, sometimes the consumer's expectation is unnecessary. The broker shows exactly which interaction and which consumer are affected.

Common Mistakes

Over-Specified Expectations

Contracts Passed Around by Copy-Paste

Using Contract Tests as Functional Tests

FAQ

What is contract testing?

A way to test the interface between two services in isolation. The consumer records the requests it makes and the responses it relies on; the provider replays them against its real implementation. If either side changes incompatibly, a test fails in that side's pipeline before deployment.

What is the difference between Pact and OpenAPI-based contract testing?

Pact is consumer-driven: contracts come from consumer tests and describe only what consumers actually use. OpenAPI-based testing is specification-first: both sides are checked against a published description of the whole API. Bi-directional contract testing combines them by comparing consumer expectations with the provider's specification.

Do we still need end-to-end tests?

A few. Keep end-to-end tests for the most important user journeys and for behavior that only emerges across the whole system. Contract tests should replace end-to-end tests whose main purpose was checking that services still fit together.

Can contract testing work for events and message queues?

Yes. Message contracts describe the payloads consumers expect, and providers verify they can produce them. Schema registries with compatibility rules add a second safeguard at publish time.

Who owns a contract?

Both teams. The consumer owns its expectations; the provider owns verification and the decision on how to evolve the API. The broker provides a shared, visible record so neither side changes the interface by surprise.

Related Topics

References