# What is contract testing?

Contract testing checks that two services agree on the requests and responses they exchange, without running both services together.

Last updated September 29, 2026, 8 min read

## Learning objectives

After reading this article you will be able to:

-   Define contract testing
-   Explain consumer-driven contracts
-   Compare contract tests with integration tests

## Related content

-   [What is software testing?](https://specstory.com/learning/testing/software-testing)
-   [What is API testing?](https://specstory.com/learning/testing/api-testing)
-   [What is integration testing?](https://specstory.com/learning/testing/integration-testing)
-   [What is the difference between mocks, stubs, and fakes?](https://specstory.com/learning/testing/mocks-vs-stubs)
-   [What is the test pyramid, and does it hold for AI-written code?](https://specstory.com/learning/testing/test-pyramid)

## What is contract testing?

Contract testing is a type of [integration testing](https://specstory.com/learning/testing/integration-testing) that checks each side of an interface separately against a shared contract. The contract records the requests a consumer sends and the responses it expects from a provider. Each side runs its check in its own continuous integration and delivery ([CI/CD](https://specstory.com/learning/ci-cd/ci-cd)) pipeline, so neither test needs the other service running.

The International Software Testing Qualifications Board (ISTQB) [defines contract testing](https://glossary.istqb.org/en_US/term/contract-testing) as a type of integration testing that checks that interfaces are used as their contracts specify. The ISTQB does not say which side writes the contract. Consumer-driven tools write it from the consumer's side, as a record of what each consumer reads.

In [software testing](https://specstory.com/learning/testing/software-testing), contract tests suit systems split into services that separate teams release on their own schedules. The consumer is the service that calls an interface, and the provider is the service that answers. An [API test](https://specstory.com/learning/testing/api-testing) checks what a provider does with a request. A contract test checks that the provider still returns what its consumers read, so a breaking change fails in the provider's pipeline instead of in production.

## How does contract testing work?

Consumer-driven contract testing is the style in which the consumer's tests produce the contract and the provider checks its responses against it. Open-source tools, e.g. [Pact](https://docs.pact.io/), run the check in seven steps across two pipelines:

1.  The consumer's test lists each request it sends and the response fields it reads.
2.  The test runs the consumer's code against a mock provider, a [test double](https://specstory.com/learning/testing/mocks-vs-stubs) that checks each request and returns the stated response.
3.  When the test passes, the tool writes these interactions to a contract file in JSON.
4.  The consumer's pipeline shares the file, e.g. through a contract broker.
5.  The provider's pipeline sets up each provider state, the data a request depends on, e.g. a cart that holds items.
6.  A verifier replays each request against the real provider and compares each response with the contract.
7.  The verifier fails when a response is missing a field the consumer reads or returns that field with another type.

Diagram: How a contract passes from consumer to provider

The two services never run together. The contract file is what passes from the consumer's pipeline to the provider's.

Ian Robinson described consumer-driven contracts in [a 2006 article](https://martinfowler.com/articles/consumerDrivenContracts.html). The provider's contract is the sum of what its consumers need, so the provider can change anything that no consumer reads. A breaking change, e.g. renaming a field that a consumer reads, fails the contract. An added field is usually safe, because a consumer reads only the fields it needs.

Teams use the term "contract test" for three kinds of check:

-   **Consumer-driven contracts.** The consumers' tests generate the contract, as in the steps above.
-   **Provider contracts.** The provider publishes a description of its API, e.g. an [OpenAPI](https://spec.openapis.org/oas/latest.html) document that lists paths, requests, and response schemas. Some teams call this provider-driven contract testing. Tests check the provider's responses, or the consumer's mocks, against the description. It lists what the provider offers, but it does not show which fields each consumer reads or which consumer a change would break.
-   **Checks of test doubles.** Martin Fowler [uses the term](https://martinfowler.com/bliki/ContractTest.html) for a scheduled test, often once a day, that checks a team's test doubles against the real outside service.

## What is an example of a contract test?

Here is an illustrative example. Acme Co. sells furniture online. Its checkout service reads carts from a cart service that another team owns and releases. The checkout team keeps this consumer test, written with Pact's JavaScript library and Jest:

```js
const { PactV4, MatchersV3 } = require("@pact-foundation/pact");
const pact = new PactV4({ consumer: "checkout", provider: "cart-service" });

test("checkout reads the items in a cart", () =>
  pact
    .addInteraction()
    .given("cart C-2210 has items")
    .uponReceiving("a request for cart C-2210")
    .withRequest("GET", "/carts/C-2210")
    .willRespondWith(200, (res) =>
      res.jsonBody({ items: MatchersV3.eachLike({ sku: "oak-chair" }) }))
    .executeTest(async (server) => {
      const cart = await getCart(server.url, "C-2210");
      expect(cart.items[0].sku).toBe("oak-chair");
    }));
```

The contract then catches a change in five steps:

1.  The test passes and writes `pacts/checkout-cart-service.json`, which the checkout pipeline shares with the cart team.
2.  A developer on the cart team asks a [coding agent](https://specstory.com/learning/ai-coding/coding-agent) to "Let customers edit their delivery address during checkout."
3.  The agent adds an `address` field to the cart response and also renames `items` to `lines`, which the task did not ask for.
4.  The agent updates the cart service's own tests to use `lines`, and they pass.
5.  The cart pipeline replays the checkout request against the changed service, and the verification fails.

The verifier's output, shortened here, names the missing field:

```text
  a request for cart C-2210
     Given cart C-2210 has items
    returns a response which
      has status code 200 (OK)
      has a matching body (FAILED)
...
    1.1) has a matching body
           $ -> Actual map is missing the following keys: items
```

If the change had shipped, checkout would have found no `items` field and shown an empty cart next to the edited address. The developer gives the verifier's output to the agent, which restores `items` and keeps `address` beside it. The contract does not list `address`, so the verification passes.

This example is simplified. A real project would record each call checkout makes, e.g. the request that saves the address.

## What changes when a coding agent writes the code?

A coding agent that changes a provider usually runs the provider's own tests, and it can edit them to match its change, as it did with `lines` in the example. The consumers' code lives in other repositories that the session does not run, so no test in the session reports a break.

A contract recorded by the consumer is a check that the agent did not write, so it can catch the break. The rename in the example was an [out-of-scope edit](https://specstory.com/learning/verification/out-of-scope-edits), one way [agents break working features](https://specstory.com/learning/debugging/agents-break-working-features). The agent can still weaken the check from either side. In the provider's repository, it can skip or delete the verification test. In the consumer's repository, the contract comes from the consumer's tests, so an agent that edits those tests also edits the contract.

A team can make provider verification a [required status check](https://specstory.com/learning/ci-cd/required-status-checks) on each [pull request](https://specstory.com/learning/code-review/pull-request), so a failed contract stops the merge. The provider team can also review each PR that changes the consumer's contract tests. The consumer team can review each PR that changes the provider's verification test.

## What are the limits of contract testing?

A contract covers the agreement between two services and little else, which sets these limits:

-   **Shape, not behavior.** The verifier checks that a response holds the fields a consumer reads, usually by type. A wrong order total of the right type passes. Pact's documentation leaves functional testing of the provider to the provider's own tests.
-   **Only recorded calls.** A request that no consumer test makes is not in the contract, so a change to it goes unchecked.
-   **Both teams must take part.** A consumer contract checks nothing until the provider runs it. Pact's documentation says the approach does not suit public APIs, whose consumers cannot be identified.
-   **No whole journey.** A passing contract does not show that a customer can finish a checkout. [End-to-end testing](https://specstory.com/learning/testing/end-to-end-testing) runs a whole journey through the finished software.

## How is contract testing different from integration testing?

Integration testing runs two or more real parts together and checks what happens where they meet. Contract testing checks each side of one boundary alone against a recorded contract, so neither team starts the other's service.

An integration test can catch what the real pair does together, e.g. a cart service that saves a valid address but drops the items. A contract test usually runs faster, runs in each team's own pipeline, and names the consumer whose expectation broke. In the [test pyramid](https://specstory.com/learning/testing/test-pyramid), many teams use contract tests in place of broad integration tests that start several services. They keep a few end-to-end tests for the journeys customers depend on.

## FAQs

### What is a breaking change?

A breaking change is a change to an interface that code on the other side cannot handle, e.g. renaming a field that a consumer reads. An added field is usually safe, because consumer code that reads only the fields it needs keeps working.

### Does contract testing replace end-to-end tests?

Contract testing does not replace end-to-end tests. A contract checks the messages between two services, so a passing contract does not show that a customer can finish a checkout. Many teams use contract tests in place of broad integration tests and keep a few end-to-end tests for the main journeys.

### Can you contract test an API you do not own?

A contract test of an API you do not own can check only your side, because the provider does not run your contract. If the provider publishes an OpenAPI document, your tests can check your mocks against it. You can also check your test doubles against the real service on a schedule, as Fowler describes.

### Is an OpenAPI schema a contract test?

An OpenAPI schema is a contract, not a contract test. The schema describes an API's paths, requests, and responses, and tests check real responses or mocks against it. The schema does not show which fields each consumer reads, so a provider can remove a field from its code and its schema, and its own checks still pass.

---

Source: [Contract testing | Consumer-driven contracts | SpecStory](https://specstory.com/learning/testing/contract-testing)
