Test Types Deep Dive · 4 of 8

Contract Testing — Provers Producers and Consumers Still Agree

When two services talk to each other, the bug nobody catches is "the producer changed the response shape and the consumer didn't notice." Contract testing makes the wire shape between them an explicit, versioned contract — verified by both sides independently, with no need to spin up the whole environment. The right answer for microservices that need to deploy independently.

PactOpenAPICDCSchemaMicroservices
← Back to Testing
Quick Facts

What a Contract Test Is

Basic Concepts

  • Producer: the service that publishes the API (or the events).
  • Consumer: the service that calls it (or subscribes).
  • The contract: a machine-readable description of what the producer returns and what the consumer expects. JSON schemas, Protobuf messages, Pact files.
  • Two flavors of testing:
    • Schema-based: producer's spec (OpenAPI, Protobuf) is the source; both sides validate against it.
    • Consumer-Driven (CDC): the consumer writes example interactions; those become the contract the producer must satisfy.
  • Why it's not just integration testing: integration tests run the consumer against a real producer. Contract tests verify the agreement without running both sides at once — each can be tested in its own pipeline.
The Two Approaches

Schema-First vs Consumer-Driven

Schema-First — OpenAPI, Protobuf, AsyncAPI

The producer publishes a schema (OpenAPI YAML, .proto, AsyncAPI). Both sides validate against it:

  • Producer side: CI runs every endpoint's response through the schema. A field that drifts off-spec fails the build. Tools: Schemathesis, Dredd, Spectral, Buf for Protobuf.
  • Consumer side: generate a client from the schema (OpenAPI Generator, openapi-typescript, Buf). Compile-time errors when the consumer expects a field the schema doesn't have.

Best for stable, well-governed APIs — public APIs, partner contracts, gRPC. The schema is the source of truth.

Consumer-Driven Contracts — Pact

The consumer writes example interactions: "when I GET /users/42, I expect a JSON object with name and email." Pact captures these as a contract file. The producer's CI runs against the contract file and verifies its real responses satisfy the consumer's expectations.

Why CDC: the consumer's tests describe the parts they actually use. The producer can change anything else freely. Adding a field is fine; removing one a consumer relies on fails the consumer's contract on the producer's CI.

The Pact Broker

Where contracts and verification results live. PactFlow (managed) or self-hosted Pact Broker. Tracks which versions of which consumers verified against which producers — and gates deployments via can-i-deploy: "can consumer v3.4 safely deploy given the producer versions currently in production?"

Async Contracts

Same idea for events. Pact has message-pact for queue/Kafka contracts. AsyncAPI is the schema-first equivalent of OpenAPI for events. Schema registries (Confluent Schema Registry, Apicurio, Buf Schema Registry) enforce backward/forward compatibility on every produced event.

Why It Wins

What Contract Testing Buys You

Test Without the Environment

The consumer's tests run in isolation against a stub satisfying the contract. The producer's CI verifies its responses still match. Neither needs the other running. PR pipelines stay fast; the answer "did I break my consumers?" is computed at PR time.

Independent Deployability

This is what microservices were promised to deliver. With contracts, the producer team can deploy whenever its contract is unbroken; the consumer team can deploy whenever it satisfies its expected response shape. No release trains.

Documentation That Stays Honest

OpenAPI / Protobuf schemas double as documentation. With CI verification, they can't drift out of sync with the code — the build fails before that happens.

Where It Hurts

The Real Costs

Pact Has a Learning Curve

Matchers, state setup, broker workflows, can-i-deploy. Teams that adopt Pact without budget for the ramp-up often abandon it within a quarter. Provider-state setup, in particular, requires real engineering investment.

You Need a Broker

Contracts in source files only work for tiny systems. Past 3-4 services, you need a broker — managed (PactFlow) or self-hosted — and someone owning it. The broker is now part of your platform.

Not a Substitute for Integration Tests

Contracts verify the wire shape. They don't verify the producer's semantics — does it actually compute the right answer? That still needs the producer's own integration tests. Contracts are for compatibility, not correctness.

Public APIs Want OpenAPI, Not Pact

For public APIs with unknown consumers, you can't use CDC — there's no consumer test to consume. Use schema-first (OpenAPI / Protobuf), version explicitly, and treat the schema as a public contract.

Decision

When to Pick Contract Testing

SetupApproach
Multiple internal services that need to deploy independentlyPact (CDC) + a broker.
Public REST API with known partnersOpenAPI + Schemathesis on the producer side.
gRPC service mesh inside one companyProtobuf + Buf breaking-change checks.
Event-driven architectureSchema registry (Avro/Protobuf) + AsyncAPI; or Pact message-pact.
Two services owned by the same teamProbably overkill — integration tests cover it.
Continue

Other Test Types