Skip to content
agentscamp
Guide · Testing

Contract Testing for Microservices: Consumer, Provider, and CI

Catch breaking service changes before deployment with consumer-driven contracts, provider verification, state fixtures, compatibility gates, and schema tests.

6 min readAgentsCamp
Updated Aug 4, 2026
contract-testingmicroservicesapitestingci

Contract tests verify that a provider still satisfies the requests and response fields its consumers depend on. Consumers publish minimal interaction contracts; providers replay them against the real implementation in CI; a compatibility gate prevents either side from deploying an incompatible version. They complement schema, unit, integration, and end-to-end tests.

Steps at a glance

  1. Inventory the real interaction. Trace the consumer code to identify the exact request, status handling, fields, types, and enum values it depends on.
  2. Write the minimal consumer contract. Run the consumer against a contract mock and assert only its real dependencies with flexible matchers.
  3. Publish and version the contract. Associate the contract with the consumer version and environment or branch so providers can verify the relevant compatibility matrix.
  4. Verify against the provider. Replay contracts against the real provider implementation with deterministic provider-state setup in provider CI.
  5. Gate deployment and evolve safely. Block incompatible provider or consumer versions, then use additive changes and staged removal for breaking evolution.

Key takeaways

  • The consumer owns its expectations because only the consumer knows which fields, types, statuses, and behaviors it actually uses.
  • Contracts should assert the minimum dependency with flexible matchers, not freeze entire example responses.
  • Provider verification runs contracts against the real provider with deterministic provider-state fixtures in the provider's CI.
  • Version and tag contracts by consumer build or environment, then block deployments whose consumer-provider matrix is incompatible.
  • Use schema tests for broad interface shape, contract tests for real consumer usage, and a small end-to-end suite for deployed-system behavior.

Consumer-driven contract tests prove that an API provider still satisfies the behavior its consumers use. They move cross-service compatibility checks out of a slow shared environment and into each service's CI, where a breaking response or request change can fail before deployment.

The consumer is the source of expectations because the provider cannot know which details every client depends on. One client may read only id and status; another may branch on a specific error code. A complete API schema describes possibilities. A consumer contract records actual dependency.

Where contract tests fit

A balanced service test strategy has several layers:

LayerProvesDoes not prove
UnitLocal business logicReal interface wiring
SchemaRequests and responses conform to an API specificationA deployed consumer is compatible
Consumer contractProvider satisfies one consumer's used interactionInfrastructure and full workflows work
Provider componentProvider works with database and close dependenciesReal multi-service deployment works
End-to-endCritical journey works in a deployed systemFast, exhaustive compatibility coverage

Contract tests reduce the number of cross-service failures that reach end-to-end tests. They do not remove the need for a small suite that checks DNS, authentication, deployment configuration, queues, and other real infrastructure.

The consumer writes the interaction

Suppose checkout calls an order service:

GET /orders/ord_42

The response may contain twenty fields, but checkout reads only:

{
  "id": "ord_42",
  "total": 4250,
  "status": "open"
}

The contract should assert:

  • the method and path
  • request body and meaningful headers
  • response status
  • id is a string
  • total is a number in the expected unit
  • status belongs to the values checkout handles

It should not assert exact timestamps, ordering of unrelated fields, internal headers, or the full provider response. Over-specified contracts turn harmless provider evolution into noise, teaching teams to ignore failures.

The consumer test runs its real client code against a contract mock. That proves the published contract matches what the consumer actually sends and parses rather than a document someone wrote separately.

Use semantic matchers

Prefer matchers that express dependency:

  • type: string, integer, boolean, array, object
  • required field presence
  • enum or regular-expression constraint
  • minimum array length when the consumer requires data
  • unordered collection when order is irrelevant
  • exact value only when the consumer truly branches on it

An example value makes a contract readable; the matcher states what may vary. Distinguish “looks like the example” from “must equal the example.”

Avoid the opposite failure too: a contract that says only “returns 200 and an object” protects nothing. If the consumer reads a field or makes a decision from it, encode that dependency.

Provider states make verification deterministic

Each interaction needs a named provider state such as:

  • order ord_42 exists and is open
  • customer has no active subscription
  • inventory is unavailable

During provider verification, the harness asks the provider test environment to establish that state, then replays the consumer request against the real handler.

Provider states are setup hooks, not alternate provider behavior. They may seed a database, stub a third-party dependency, or select a fixture. Keep them deterministic, isolated, and idempotent so verification can run in parallel and in any order.

Test success and meaningful failure contracts. Consumers often depend more strongly on error status, machine-readable error codes, and retry semantics than on the happy response.

Publish contracts as versioned artifacts

A contract belongs to a specific consumer build. Publish it to a broker or another versioned store with:

  • consumer and provider names
  • consumer commit or build version
  • branch or environment tag
  • verification results by provider version
  • deployment status when available

Do not overwrite one latest.json file. Compatibility is a matrix: provider version P may satisfy consumer versions A and B but not C. Deployment gates need that history to answer whether the exact versions entering an environment can coexist.

For small systems, contracts can live in repositories, but a broker becomes valuable when many consumers, versions, and independent deployment pipelines must coordinate.

Verify in provider CI

When a provider changes, its pipeline should fetch relevant contracts and replay them against the real provider implementation. A failure reports:

  • the consumer and interaction
  • expected request or response condition
  • actual result
  • provider state
  • versions being compared

Make verification a required check. A dashboard that says “incompatible” after the provider deploys is monitoring, not prevention.

Run verification for the consumers currently deployed and those preparing to deploy. Verifying every historical contract forever can prevent intentional cleanup long after old consumers disappear.

Evolve interfaces with both sides visible

For a breaking field change:

  1. Provider adds the new field while preserving the old one.
  2. Consumer publishes a contract using the new field.
  3. Provider verifies both old and new contracts.
  4. Updated consumer deploys and old versions retire.
  5. Provider removes the old field only when the compatibility matrix shows no active consumer needs it.

This is expand-and-contract applied to interfaces. Contract history tells you when contraction is safe.

Provider teams should not edit consumer contracts to make a provider build pass. If the expectation is wrong, change it through the consumer's code and tests so the artifact remains tied to actual usage.

Avoid common contract-test traps

  • Testing provider implementation details: Contracts describe observable boundary behavior.
  • Snapshotting whole responses: Match only dependencies.
  • Mocking the provider during provider verification: Verify the real handler and serialization layer.
  • Non-deterministic provider states: Seed explicit data and isolate tests.
  • Ignoring asynchronous messages: Events and queues need message contracts for payload, key, headers, ordering assumptions, and evolution.
  • Skipping the deployment gate: Verification without enforcement still allows incompatible releases.
  • Treating contracts as business-logic tests: Keep provider behavior coverage in its own test suite.

Start with one painful boundary

Choose an integration that often breaks in staging or forces teams to coordinate deployments. Add contracts for the consumer's critical success and error paths, provider verification in CI, and a compatibility gate. Measure whether cross-service defects move earlier and whether the end-to-end suite can shrink.

TIP

A good contract is intentionally incomplete: it protects everything this consumer needs and permits everything it does not care about.

Use the Contract Test Designer to generate consumer and provider setups for the repository's framework, and pair it with the OpenAPI Doc Writer so implementation, public schema, and real consumer expectations converge.

Frequently asked questions

What is consumer-driven contract testing?
It is a testing method where each API consumer records the requests it sends and the response elements it relies on. The provider verifies those contracts against its real implementation before deployment, catching consumer-breaking changes without a shared end-to-end environment.
Are contract tests the same as OpenAPI validation?
No. OpenAPI validates a provider-defined schema and can describe the whole API. Consumer contracts capture the narrower behavior each real consumer uses, including specific interactions and provider states. Use both: schema conformance for the public interface and contracts for compatibility with deployed consumers.
Do contract tests replace integration tests?
No. They verify the boundary between services. Unit tests still own business logic, component or integration tests own the provider with its database and dependencies, and a small end-to-end suite verifies deployment, networking, identity, and critical journeys.
Why should contracts use matchers instead of exact JSON?
Exact example matching over-specifies irrelevant values and makes harmless provider changes fail. Match the fields and constraints the consumer uses—types, required keys, enums, and status—while allowing unrelated fields and values to evolve.

Related