What Is Pact Contract Testing? Consumer-Driven Contracts, Fairly Compared
Pact turns each consumer's expectations into a contract the provider must verify. See how it works on one interaction, where it beats spec-driven contracts, where it doesn't, and where Shift-Left API fits.
This is lesson 5.7 of OpenAPI, Contract and Schema Testing. Lesson 5.6 named two places a contract can live; this lesson is about the second, consumer-driven contracts, and it is mostly concept. No product feature is needed for the first two thirds.
What is Pact contract testing?
Pact contract testing is consumer-driven: each client of an API writes down the requests it makes and the parts of the response it reads, and the API's team verifies that the real API still satisfies every client's list. Pact is the open-source tool that does this. A consumer's expectations become a pact file; the provider replays the requests in it against the real API; and a deployment is allowed only when every verified contract still passes.
The idea behind it: an API only needs to keep the promises someone relies on. A field no client reads can be renamed freely. A field one client reads can't.
How Pact works, in four steps
- The consumer writes a test. In its own codebase and language, the client team describes an interaction: a request, and the response it expects. The test runs against a Pact mock provider, so it is fast and needs no real API.
- Pact writes a contract. A passing consumer test produces a pact file: a JSON record of each interaction.
- The contract is shared. Pact files are published to a Pact Broker (open source) or PactFlow (hosted), versioned by consumer and provider.
- The provider verifies it. The provider's build fetches every consumer's pact, replays each request against the real API, and checks the responses. Before a deploy, the
can-i-deploycheck asks the broker whether this version has passed against everything it will meet in production.
Provider states connect the two sides: an interaction says "given order ffffffff-... exists", and the provider sets that data up before replaying the request.
A Pact contract testing example
Picture an order page that calls the sandbox store once:
/api/v1/orders/ffffffff-ffff-ffff-ffff-ffffffffffff?expand=productcurl -X GET 'https://demo.totalshiftleft.ai/api/v1/orders/ffffffff-ffff-ffff-ffff-ffffffffffff?expand=product'The response carries more than a dozen fields. The order page reads two: data.status and data.product.name. Its pact describes only those, matched by type rather than exact value. Simplified, in the style of a pact file:
{
"consumer": { "name": "order-page" },
"provider": { "name": "store-api" },
"interactions": [{
"description": "a request for a pending order with its product",
"providerState": "order ffffffff-ffff-ffff-ffff-ffffffffffff exists",
"request": {
"method": "GET",
"path": "/api/v1/orders/ffffffff-ffff-ffff-ffff-ffffffffffff",
"query": "expand=product"
},
"response": {
"status": 200,
"body": { "data": { "status": "pending", "product": { "name": "Standing Desk" } } },
"matchingRules": {
"$.body.data.status": { "match": "type" },
"$.body.data.product.name": { "match": "type" }
}
}
}]
}
Now the provider team removes notes from orders. The pact still passes: the order page never read it. Then they rename product.name to product.title. Verification fails, the broker records it, and can-i-deploy stops the release before the order page breaks.
Consumer-driven vs spec-driven, fairly
The spec-driven alternative, covered in lesson 5.6, makes the provider's OpenAPI spec the contract. Neither approach is better everywhere:
| Consumer-driven (Pact) | Spec-driven (OpenAPI) | |
|---|---|---|
| Contract comes from | Each consumer's tests | The provider's spec |
| Knows which fields clients use | Yes, precisely | No; it must assume any documented field is in use |
| Works for unknown clients (public APIs) | No: a consumer must write a pact | Yes |
| Covers | The interactions consumers wrote | Every documented operation and rule |
| Who does the work | Every consumer team, in code | The provider team, mostly |
| Extra infrastructure | A broker, and verification in every provider build | A spec diff in CI |
| Catches consumer misuse | Partly, in the consumer's own tests | No |
Pact's strength is precision: it lets providers change anything nobody depends on, and it proves a deploy is safe for the specific clients in production. Its cost is coordination: every client team must adopt it, write and maintain tests, and keep provider states working. Between internal services owned by teams who talk to each other, that trade is often worth it. For a public API with unknown clients, it can't work alone, because there is no one to write the pacts.
Contract testing tools like Pact
- Pact, with libraries for JavaScript, the JVM, .NET, Go, Python, Ruby, Rust and more, and the Pact Broker or PactFlow to share contracts.
- Spring Cloud Contract, on the JVM: contracts written in a DSL, from which it generates provider tests and stubs for consumers.
- Spec-driven tools, which treat OpenAPI as the contract: Specmatic generates contract tests and stubs from a spec; Schemathesis generates property-based tests from it.
- Bi-directional contract testing, in PactFlow, which compares consumer pacts with the provider's OpenAPI spec instead of replaying requests.
For a hands-on Pact tutorial with CI, read Pact contract testing: a hands-on tutorial with CI.
Pact contract testing alternatives: choosing
Ask three questions:
- Do you know all your consumers? If not, you need a spec-driven contract for at least the public surface.
- Will consumer teams write and maintain contract tests? If not, a consumer-driven program stalls after the first service.
- Is your spec accurate? If not, spec-driven contracts test the wrong thing until it is fixed.
Many organisations use both: spec-driven contracts for the published API, Pact between internal services, and bi-directional checks where they meet.
Where Shift-Left API fits
Shift-Left API is a spec-driven tool, on the provider side. It does not read or write pact files, and it has no broker or can-i-deploy step, so it doesn't replace Pact for consumer-driven contracts. What it does on the spec-driven side:
- Generates provider tests from the OpenAPI spec (lesson 5.3).
- Classifies each re-imported spec change as breaking or non-breaking before it is applied (lesson 5.5).
- Compares the responses of passing tests with the documented response schema.
- Spins up mock servers from the spec, so consumers can build against the documented answers (lesson 9.9). The mocks follow the spec's examples or schema placeholders; they don't record or replay consumer expectations.
You can still write a consumer's expectations as a test against the provider, as in the lab below. That checks the provider today; it is not a pact, because no consumer build produces it. The API contract testing page describes the spec-driven workflow.
Try it yourself
Write the order page's contract as one Shift-Left API test: Get a single order by ID on ffffffff-ffff-ffff-ffff-ffffffffffff, with expand set to product. Add two Quick JSON assertions: data.status is one of pending, processing, completed, cancelled, and data.product.name is of type string. Run it. Then ask: which fields of the response is the order page free to lose?
Recap
- What is Pact contract testing? Consumers record the interactions they rely on, and providers verify every consumer's pact before they deploy.
- A Pact contract testing example covers only the fields a consumer reads, so providers can change everything else safely.
- Contract testing tools like Pact suit internal services with known consumers; spec-driven contracts are the main Pact contract testing alternative for public APIs, and many teams use both.
Next: lesson 5.8, an API versioning example, for when a contract has to break.
Try it: Write the order page's contract as one Shift-Left API test: Get a single order by ID on the pending order ffffffff-ffff-ffff-ffff-ffffffffffff with expand set to product, with Quick JSON assertions that data.status is one of pending, processing, completed, cancelled and that data.product.name is of type string.
Frequently asked questions
What is a Pact contract testing example?+
An order page calls GET /api/v1/orders/{id}?expand=product and reads two fields: the order's status and the product's name. Its Pact test describes that request and those two fields, runs against a Pact mock of the provider, and writes a pact file. The provider team then replays the request against the real API and checks the two fields are still there with the same types.
What are some contract testing tools like Pact?+
Pact is the best-known consumer-driven tool, with a Pact Broker or the hosted PactFlow to share contracts. Spring Cloud Contract, on the JVM, generates provider tests and stubs from contracts written in a DSL. Spec-driven tools treat the OpenAPI document as the contract instead, generating conformance tests and mocks from it; Specmatic and Schemathesis are examples, and so is Shift-Left API.
What are the alternatives to Pact contract testing?+
The main alternative is spec-driven contract testing: the provider's OpenAPI spec is the contract, every change to it is diffed for breaking changes, and the running API is tested against it. It suits public APIs and clients you don't control. Bi-directional contract testing, offered by PactFlow, combines the two by comparing consumer pacts with the provider's spec.
Related lessons
A six-step method for API contract testing, from choosing where the contract lives to gating every release on it, with a contract suite built as a Test Run Pack in Shift-Left API.
One field becomes required, and every old client breaks. Work through that change with each versioning strategy, see which tests a compatible release needs, then test both spec versions in Shift-Left API.
Run your tests before the real service is ready, or while it's down. Generate mocks from the spec in Shift-Left API, start the mock server, and send a test to it by switching one environment.
Read more on the blog
A runnable Pact contract testing tutorial: a pact-js consumer test, publishing to a Pact Broker or PactFlow, provider verification with pact-js or pact-jvm, can-i-deploy in CI, common pitfalls, and how Pact compares with schema-first OpenAPI and bi-directional contract testing.
Pact vs Postman for contract testing — what consumer-driven contracts prove that schema assertions cannot, where Postman is genuinely enough, and how to run both.