Case Study How Laurel brings production-ready validation to AI-native development while cutting change failure rate by 82%

Contract Testing: The Complete Guide

What a contract test verifies, how consumer-driven and provider-driven contracts differ, how the practice compares to unit and integration testing, which tools fit which team, and where contract tests still miss.

Contract testing verifies that a consumer service and a provider service agree on the shape of the API call between them, without running either service’s full dependency graph. One side writes a machine-checkable description of the interaction, and the other side runs that description against its real code.

The practice exists because integration testing every pair of services does not scale to how independently microservices teams ship. If every team had to run a full suite against every other team’s current code before deploying, the coordination would cost more than the checks are worth. Contract testing narrows the check to the interface itself, so each side verifies compatibility without running the other.

That narrowness is the whole trade. A contract test proves two services agree on the shape of an interaction. It does not prove the system behaves correctly when that interaction happens against the current, deployed version of everything else. Coding agents press on that gap from both ends, changing consumer and provider in the same session and arriving as a class of API consumer no contract describes. At Bitso, agent-authored pull requests rose from 55 percent to 76 percent while change failure rate fell from 1.1 percent to 0.2 percent, which took validation that scaled with the volume.

This guide covers what a contract test verifies, how contracts get written, how the practice compares to unit and integration testing, which tools fit which team, where contract tests stop covering the system, and how to decide whether yours needs them. Contract testing is one layer of a wider practice, and the complete guide to microservices testing covers the rest.

What is contract testing, and what is an API contract?

A contract test checks one thing: whether a consumer’s understanding of a provider’s API matches what the provider returns. It runs at the boundary between exactly two services, without booting the rest of the system, which is what makes it cheap enough to run on every change.

The contract is a machine-checkable description of one interaction, usually an HTTP request and response pair, or a message and the shape it takes on a queue. It covers the request method and path, required and optional fields, field types, and the status codes and error shapes the provider can return.

Anything the two sides never wrote down sits outside it, which is where most of the interesting failures live.

A contract test is not a substitute for running the consumer and provider together. It checks both sides against a shared description, not against each other, so two services can each pass their own contract tests and still fail when they talk.

A REST API contract testing example

A checkout service calls an orders service for GET /orders/{id} and reads three fields off the response: id, status, and total_cents. An API contract test for that pair records the request and those three fields, with their types.

The orders team later renames total_cents to total, which its own specification permits because the field was never marked stable. Running checkout’s contract against the new orders code fails at that field, by name, before the change merges. Nothing else about the orders service has to be running for the check to work.

Now change the example. The orders team keeps total_cents but starts returning it as a string instead of an integer, on refunded orders only. If the contract recorded a type, the check catches it. If the contract recorded only that the field exists, every test passes and checkout breaks on the first refund.

That second case is the one worth holding on to. A contract is exactly as good as what someone thought to put in it, which is the subject of a later section.

Contract testing vs integration testing vs unit testing

These three check different scopes, and none is a superset of another. Passing a cheaper one does not imply the more expensive one would pass.

A unit test runs one function or one small unit inside a single service, with its dependencies mocked. It is the fastest of the three and says nothing about how the service behaves when it talks to anything else.

A contract test runs at the boundary between two services. It confirms that a consumer’s expectations of a provider’s interface match what the provider returns, with neither side’s other dependencies present.

An integration test runs multiple real services together along an actual call chain. It is the slowest to set up and run, and the only one of the three that catches a failure appearing when several services interact at once: an ordering problem, a timeout under load, or a change that is correct at every boundary and wrong in combination.

unitweborderspaymentscontractintegration
Each level tests a different span of the same call chain. Shaded services and bold arrows are what the test exercises.
unitweborderspaymentscontractintegration
Each level tests a different span of the same call chain. Shaded services and bold arrows are what the test exercises.
Test typeScopeDependenciesSpeedCatchesMisses
UnitOne function or unit inside one serviceNone, mocked or stubbedFastestLogic bugs inside a serviceAnything outside the service
ContractThe boundary between exactly two servicesNone, the contract stands inFastInterface mismatches between two servicesBehavior the contract does not describe
IntegrationMultiple real services along a call chainReal or near-real, runningSlowestMulti-service and end-to-end behaviorLittle structurally, but often too slow for every change

Contract testing vs integration testing

This is the comparison people reach for most, and the two are not tiers of one ladder. Contract testing checks the interface between two services in isolation. Integration testing runs the services together and checks what happens.

The first is cheap enough for every commit. The second is the only one that sees behavior, because behavior is what emerges when two systems talk to each other for real.

Run them at different points rather than choosing between them. Contracts belong before merge, where a fast and specific failure is worth more than a thorough one. Integration tests belong against live dependencies straight after, where the cost of running the real thing buys coverage no contract can give. Four tactical models for shifting testing left walks through where each check lands in a pipeline.

Consumer-driven vs provider-driven contracts

Contracts originate on one side of the interaction or the other, and which side writes them decides who is protected.

In a provider-driven contract, the provider publishes a specification, commonly an OpenAPI document, describing every endpoint it offers. Consumers build against that specification, and the provider tests its own service against what it published. One document and one owner keeps the process cheap to run.

It also protects the provider’s intent rather than how consumers use the API. A provider can satisfy its own specification and still break a consumer that depended on a field the spec marked optional but that consumer treated as always present.

Consumer-driven contract testing reverses the order. Each consumer records the exact requests it sends and the responses it needs, based on how it calls the provider rather than on what the documentation permits. That recording becomes the contract. The provider then runs every consumer’s contract against its own code, usually on every change, and a failure means a named consumer would break if the provider shipped as is.

consumercontractproviderwritesverifies
In a consumer-driven contract, the consumer states the expectation and the provider proves it still holds.
consumerwritescontractverifiesprovider
In a consumer-driven contract, the consumer states the expectation and the provider proves it still holds.

Consumer-driven contracts catch a narrower and more relevant set of breaks: not everything the provider could theoretically change, but everything a real consumer depends on today. The cost is coordination. Every consumer has to write and maintain its contract, and a consumer that never writes one is invisible to verification. A provider with forty consumers and contracts from twelve of them is checked against twelve.

The coordination problem is well documented. A study of microservice API evolution built on 17 interviews across 11 companies found the recurring challenges were change impact analysis, ineffective communication of changes, and consumers staying on outdated versions, which the authors trace to tight organizational coupling and consumer lock-in.

Which model a team adopts usually follows from who owns the pain. If provider teams keep breaking consumers without warning, consumer-driven contracts put the check where the risk is. If the API is public or has too many consumers to coordinate individually, a provider-driven specification is often the only tractable starting point.

Contract testing tools: Pact, Specmatic, Karate, Keploy and SmartTests

Contract testing software falls into three shapes: tools built on consumer-recorded interactions, tools generated from an API specification, and tools that compare live behavior instead of a written contract. Five come up most often, and no single contract testing tool is a drop-in replacement for another. The right starting point depends on what your team already maintains and how much manual upkeep it will absorb.

Pact is the category standard. It implements consumer-driven contract testing directly, with language support across the JVM, JavaScript and TypeScript, .NET, Go, Python, Ruby and PHP, and a broker, either the self-hosted Pact Broker or the hosted Pactflow, that stores contracts and answers whether a provider is safe to deploy against every consumer’s current contract. Its strength is explicit control: a team can see exactly what is asserted and why a check failed. Its cost is that same explicitness, since every consumer maintains a contract and every provider runs verification on every change.

Specmatic takes a specification-driven route instead of a consumer-recorded one. It derives a consumer-side stub and a provider-side verification test from one OpenAPI or AsyncAPI document, so the specification becomes the single artifact both sides check against. This suits teams already maintaining an API specification as documentation, because contract testing turns into a byproduct of keeping that specification accurate. It inherits the provider-driven trade-off, describing what the provider intends to offer rather than what each consumer uses.

Karate is a broader API test automation framework that covers contract-style assertions, written in a Gherkin-like syntax non-programmers can read. Teams already running Karate for functional or performance testing can express contract checks in the same framework instead of adopting a dedicated tool, trading away the contract-specific machinery like a broker and a compatibility matrix.

Keploy generates tests, including contract-style assertions, by recording real traffic against a service and replaying it. It bootstraps coverage without anyone hand-writing tests, which helps services with weak existing suites. The trade-off is that a recording captures behavior at the moment it was taken, and nothing re-checks it against the provider’s current code the way a verification step does.

Signadot SmartTests sits in a different category from the other four. Rather than a contract file either side maintains, it runs the changed service against real deployed dependencies and diffs its behavior against the stable shared version. There is no contract to write or keep current, and the comparison happens against what is running. The trade-off runs the opposite way from Pact: it needs a live cluster to compare against, so it does not work offline against a stored contract, and a team that wants a portable specification gets a behavioral diff instead.

ToolContract modelWho writes itMaintenanceRuns offlineBest fit
PactConsumer-recorded interactionsEvery consumer, verified by the providerHigh, grows with consumer countYesMany teams on either side of an interface that need an auditable, versioned contract
SpecmaticOpenAPI or AsyncAPI specificationThe provider, onceMedium, tied to spec accuracyYesTeams whose API specification is already the source of truth
KarateAssertions in a test frameworkWhoever owns the test suiteMediumYesTeams already using Karate for API test automation
KeployRecorded traffic replayedGenerated from live callsLow to write, ages silentlyYesServices with weak existing coverage that need a starting point
Signadot SmartTestsBehavioral diff against the stable shared versionNobody, generated per changeClose to noneNo, needs a live clusterKubernetes teams that want breaking changes caught without owning contract files

API contract testing tools for OpenAPI-first teams

If your API specification is already accurate and enforced, most of the contract testing problem is solved and the remaining question is which direction to generate from it. Specmatic generates both sides from the document. Pact can be used alongside a specification, though its contracts still originate from consumers rather than from the spec.

The failure mode to watch for here is a specification that drifts from the running service. A generated check is only as current as the document it came from, and a stale OpenAPI file produces tests that pass against an API nobody is shipping. Relevancy-weighted diffs for REST API testing covers comparing against live responses instead.

Test your next change against real dependencies

Signadot spins up isolated sandboxes on the Kubernetes cluster you already run, so every change is validated against real services before it merges. The free tier is open to every developer.

Contract testing when AI coding agents write both sides

Agents break a quiet assumption behind every contract: that a person wrote both sides and knows what changed. When an agent proposes changes to the consumer and the provider in the same afternoon, the contract between them is the one artifact nobody updated.

That makes staleness the dominant failure mode. A contract is written and maintained by hand, so under agent volume it stops being the safeguard and becomes the reason a correct change fails its check. Teams respond by relaxing the check, which is how a contract suite quietly stops meaning anything.

The consumer side is shifting too. Agents call APIs directly now, and most APIs were not designed with that in mind. Postman’s 2025 State of the API report found that only 24 percent of developers actively design APIs with AI agents in mind, while 60 percent design primarily for humans and 16 percent have not considered agents as consumers at all. A consumer-driven contract protects the consumers that wrote one, and an agent calling your API has written nothing.

Set against that, contract testing holds a real advantage over every other check an agent can run. A failed contract test names the field, the type or the status code that moved. That is machine-actionable in a way a failed end-to-end test is not, so an agent can read the result and correct the change without a person translating it first. This is how Claude Code, Cursor, Codex and Copilot work: act, read the result, act again.

What none of them can use is a check on somebody else’s schedule. Testing AI-generated code against the interface it changed has to happen on every attempt, not nightly and not once per feature, and it has to finish before the change reaches a human. Otherwise review becomes the only real verification step, for work arriving faster than anyone can read it. Validating AI-generated code on Kubernetes covers how that check gets wired in.

The last requirement is the interface. An agent needs to invoke verification from a command line or an MCP server, not a console, and it needs the answer back as structured output rather than a dashboard. Why the MCP server is now a critical microservice covers what that path has to look like once agents depend on it.

Where contract testing still lets bugs through

Contract tests can pass at every layer while production breaks, because a contract only checks what it was written to check. Four gaps show up often enough to name.

The contract is narrower than the real interaction. It covers the fields both sides agreed matter. An edge case nobody wrote down, a rare error path, or the order fields arrive in is real and can break a consumer without failing a single check.

Verification lags the provider’s actual code. Consumer-driven contract testing protects a consumer only if the provider runs verification before deploying, on every change. A provider that skips it on a hotfix, or verifies against a broker holding contracts a consumer forgot to republish, ships a break no contract test saw.

Contracts age faster than anyone re-reads them. A contract written a year ago encodes what the interaction looked like then. Without something forcing a re-check against current behavior, it can describe an interaction that no longer exists, and every test built on it passes for the wrong reason.

Real infrastructure sits outside the contract. Network timeouts, retries, authentication and deployed-only configuration are not things a contract, or a mock derived from one, exercises. A contract test can pass at the exact moment the same request would fail in production.

This is the failure mode described in why integration tests pass with mocks but staging breaks. A contract, like a mock, is a snapshot of an assumption, and writing that assumption down more formally does not protect it from the dependency changing afterwards. Contract testing narrows the search for interface breaks. It does not replace a check against what is deployed.

Is contract testing worth the maintenance?

You need the check. You do not necessarily need a written contract to get it. Every team needs to know that a change to one side of an interface will not break the other, and a formal contract is one way to find that out rather than the only one.

The risk it addresses is real but narrower than it sounds. A large-scale study of API breaking changes across 317 Java libraries, 9,000 releases and 260,000 client applications found that 14.78 percent of API changes broke compatibility, while 2.54 percent of clients were affected. Those are library APIs rather than REST services between teams, so read the pair as a shape and not a forecast. Breaks are common, and most of them land on nobody. The problem is that nobody knows in advance which ones will.

For a single team owning both the consumer and the provider, skip it. Both sides sit in the same codebase, or close enough that a breaking change surfaces in review or a shared suite the same day. A contract here adds upkeep without adding signal.

For a team whose OpenAPI specification is accurate and enforced in CI, much of the value is already captured. Generating checks from that document costs little. Adding a second, consumer-recorded contract layer on top pays off only when consumers keep depending on behavior the specification does not pin down.

The argument against contract testing at scale is an argument against its maintenance cost, and it lands. Contracts go stale, and teams abandon them for that reason rather than because the idea is wrong. The answer is not to drop the check but to move it somewhere that needs no hand-maintained files. A new approach to contract testing makes that case in full.

How to choose: four questions that decide it

Whether contract testing earns its place, and which tool fits, depends on four things about your team more than on any feature comparison.

Team size

A single team owning both sides does not need a formal contract. Contract testing earns its keep once the consumer and provider belong to different teams shipping on their own schedules, so neither side can rely on the other reading its pull requests. The more teams sit on either side of an interface, the more a machine-checked contract replaces coordination that has stopped scaling.

Service count

With a handful of services, the number of consumer-provider pairs is small enough to track in a shared channel, and the overhead of maintaining contracts can exceed what it saves. Past a few dozen services the number of interface pairs grows faster than the service count itself, and nobody holds that graph in their head. That is where automatic checks start catching breaks no engineer would have spotted by inspection.

Release velocity

Teams releasing a few times a week can often afford a manual check on a risky interface change. Teams releasing continuously, and teams where agents attempt many changes an hour, cannot. A check that runs on every commit is the only version that keeps up, and it is also the only one an agent can act on directly, because it names the field or status code that moved.

Existing tooling

What a team already maintains usually decides the starting tool. An accurate OpenAPI specification makes Specmatic close to free. An existing Karate suite absorbs contract assertions without a second tool. A team with the engineering time to own contracts explicitly, and a real need for that control, is well served by Pact. A team on Kubernetes that would rather test against real dependencies than maintain any contract file is the case the next section covers.

Contract testing best practices checklist

Most contract testing best practices reduce to twelve questions worth answering yes to. Anything you cannot is where breaks get through.

  • Types, not just names. Does every contract record field types and formats rather than only that a field exists?
  • Every consumer publishes. Does each consumer publish a contract, so verification is not silently checking a subset?
  • Verification is required. Is provider verification a blocking check rather than an optional job someone can skip on a hotfix?
  • Contracts are versioned with code. Does a contract travel in the same commit as the change that altered it?
  • Failures name the field. Does a failed check identify the exact field or status code, so a developer or an agent can act without interpreting it?
  • Both sides re-verify on change. Does a provider change trigger verification against current consumer contracts, and not only the reverse?
  • Staleness is visible. Can you tell how long it has been since a contract was verified against the provider’s live behavior?
  • Error paths are covered. Do contracts describe the error shapes and status codes, not only the happy path?
  • Async interfaces are included. Are message and event schemas covered, or does the practice stop at HTTP?
  • The check runs per change. Does every change get its own verification run rather than being batched with others?
  • Agents can invoke it. Can an agent run the check through a command line or MCP interface without a person?
  • Something checks real dependencies. Is there a layer beyond contracts that exercises the change against what is deployed?

That last item is the one teams most often miss, and shadow testing for APIs covers one way to cover it.

How SmartTests approaches contract testing

SmartTests answers the maintenance side of contract testing rather than replacing the category. A Sandbox deploys only the changed version of a service into a shared Kubernetes cluster, alongside the stable shared copies of everything it depends on. Nothing is duplicated and nothing is mocked.

Instead of a consumer or provider hand-writing a contract, the same requests go to the changed version and to the stable shared version, and the two sets of responses are diffed. The diff is built to separate a change that matters, a removed field or a status code that moved from 200 to 500, from one that does not, like a timestamp or a generated identifier. What comes back is a short list of what changed, produced the moment a change is proposed, with nothing to write beforehand and nothing to update as the API evolves.

The trade-off is the one in the tooling table. SmartTests gives up the portable, auditable contract file that Pact produces, in exchange for close to no ongoing maintenance and a check that runs against currently deployed dependencies. A team that wants a versioned specification it can inspect independently of any platform should reach for Pact. A team that wants breaking changes caught on every change without anyone owning a contract file is the case this was built for.

The feature-by-feature comparison, including setup effort and maintenance time, lives on the SmartTests and Pact comparison page. SmartTests runs alongside Sandboxes and Jobs on the Kubernetes cluster you already operate.

Where to go next

If you are deciding whether to adopt contract testing at all, the four questions above are the shortest route to an answer, and AI-powered contract testing for microservices covers what changes once agents write most of the API changes. If you already run Pact and the maintenance is the problem, SmartTests compared with Pact is the direct comparison. If contract testing is one piece of a wider testing strategy you are still assembling, start from the complete guide to microservices testing.

Frequently asked questions

What is contract testing in software testing?

Contract testing verifies that a consumer service and a provider service agree on the shape of the call between them, without running either service's full dependency graph. One side writes a machine-checkable description of the interaction, and the other side runs that description against its real code. It catches interface mismatches at the boundary between two services rather than across a whole system.

What is the difference between contract testing and integration testing?

Contract testing checks the interface between two services in isolation, without deploying either side's dependencies. Integration testing runs multiple real services together and checks what the call chain does. Contract testing is faster and narrower, and it can pass while two services would still behave incorrectly together. Integration testing catches that, at a higher cost per run. Most teams past a handful of services run both, at different points in the pipeline.

What is consumer-driven contract testing?

In consumer-driven contract testing, the consumer records the exact requests it sends and the responses it needs, based on how it calls the provider rather than what the provider's documentation allows. That recording becomes the contract. The provider then runs every consumer's contract against its own code on each change, and a failure names the specific consumer that would break if the provider shipped as is.

What are the best contract testing tools?

Pact is the most established, with deep language support and a broker for sharing contracts between teams. Specmatic derives contracts from OpenAPI documents instead of recorded interactions. Karate folds contract assertions into a broader API testing framework. Keploy generates tests from recorded traffic. Signadot SmartTests skips the contract file and diffs a changed service's behavior against the stable shared version. The right one depends on what your team already maintains.

What are the best practices for API contract testing?

Record types and not only field names, version contracts alongside the code that produces them, and make provider verification a required check rather than an optional job. Publish a contract from every consumer, because an unpublished consumer is invisible to verification. Re-verify against the provider's current code on every change, not on a schedule. Pair contracts with a check against real deployed dependencies, since a contract only describes what someone wrote down.

Can contract tests pass and production still break?

Yes. A contract test verifies only what the contract describes, usually request shape, response fields, and status codes. Both sides can satisfy the contract and still fail if it never captured a behavior that later changed, if a mock built from the contract drifted from what the provider deploys, or if the failure depends on infrastructure the contract never exercises, such as timeouts, retries, or deployed-only configuration.

Do you need contract testing if you already do integration testing?

Contract testing and integration testing catch different failures, so one does not make the other unnecessary. Integration tests need real dependencies running together, which makes them slower and more expensive to run on every commit. Contract tests run in isolation and check the interface on every change at a fraction of that cost. Teams that scale past a handful of services usually keep both rather than treating them as substitutes.

Is contract testing overkill for a small number of services?

Often, yes. When one team owns both the consumer and the provider, a breaking change surfaces in code review or a shared test suite the same day it is written, and a formal contract adds upkeep without adding signal. Contract testing starts paying once the two sides are owned by different teams shipping on their own schedules, so neither side can rely on the other reading its pull requests.

How do AI coding agents change contract testing?

Agents change both the volume of API changes and the cost of maintaining contracts by hand. They propose changes on both sides of an interface faster than anyone updates the contract between them, so a stale contract becomes the bottleneck rather than the safeguard. Agents also need the check on every attempt in a build-test-fix loop, not once per feature, which rules out any verification step that waits for a person.

Can a coding agent check its own API change before opening a pull request?

It can, provided the check runs without a person in the loop and returns a specific failure. A contract test qualifies, because it names the field or status code that changed in a form an agent can read and act on. Running the changed service against real deployed dependencies catches the behavior a contract never described. Both paths need a command line or MCP interface rather than a console someone clicks.

Stay in the loop

Get the latest updates from Signadot

Validate code as fast as agents write it.