Contract Testing
Contract testing verifies that a consumer and a provider agree on the shape of an API without standing up the whole system. A consumer records its requests and expected responses into a contract; the provider verifies it honours that contract before either releases.
What is this?
Contract testing verifies that a consumer and a provider agree on the shape of an API without running them together. The consumer records the requests it will make and the minimal responses it needs into a contract file; the provider then runs a verification step proving its real code honours that contract. It catches breaking changes at integration boundaries before the services ever talk in production.
Why do you need it?
- It isolates teams: consumers and providers develop independently yet prove compatibility without shared environments.
- It runs in milliseconds instead of the hours full end-to-end suites take, so it fits in every CI build.
- It catches contract drift early, at the point where the consumer's expectation or the provider's behavior changed.
- The published contract is executable documentation of exactly how the API is used.
- Provider verification reports which consumer expectations are unmet, so changes are made deliberately, not by accident.
Mental model
A signed contract between two teams
A Pact file plus provider verification
The sales team (consumer) signs an agreement stating what they will ship and what they need from supply chain (provider). The agreement is versioned, both sides keep a copy, and any amendment goes through review. Pact automates exactly that between services.
How it works
- The consumer defines expectations using a Pact library: the request it will send and the minimal response it needs.
- The Pact framework runs those expectations against a local mock provider to produce a contract file.
- The contract is published to a Pact Broker shared with the provider team.
- The provider runs a verification step that replays the consumer's expectations against its real code.
- The provider's CI checks the verification result — a failed verification blocks the release that would break consumers.
Consumer writes the contract; the provider verifies it; the broker gates the release.
Key commands
npm install -D @pact-foundation/pactInstall the pact-js library in the consumer project.
npx pact-broker publish ./pacts --broker-base-url https://broker.example.com --consumer-app-version 1.2.3Publish the generated contract files to the broker with the consumer version.
npm test -- --testPathPattern=pactRun only the consumer contract tests to regenerate the pact files.
npx pact-broker can-i-deploy --pacticipant orders-web --version 1.2.3Ask the broker whether it is safe to deploy this consumer version.
CONSUMER | CONTRACT | PROVIDER | RESULT\norders-web | 1.2.3 | orders-api | PASS
curl -s https://broker.example.com/pacts/provider/orders-api/consumer/orders-web/latestDownload the latest contract JSON directly from the broker for inspection.
Examples
A consumer contract for getting an order
const { PactV3, MatchersV3 } = require("@pact-foundation/pact");const provider = new PactV3({ consumer: "orders-web", provider: "orders-api" }); describe("orders-web → orders-api", () => { it("gets a shipped order", () => { provider .given("an order with id 42 exists") .uponReceiving("a request for order 42") .withRequest({ method: "GET", path: "/orders/42" }) .willRespondWith({ status: 200, headers: { "Content-Type": "application/json" }, body: MatchersV3.like({ id: 42, status: "shipped", total: MatchersV3.decimalLike(120.5), }), }); return provider.executeTest((mockServer) => fetch(mockServer.url + "/orders/42") .then((res) => res.json()) .then((order) => expect(order.status).toBe("shipped")) ); });});given() declares a provider state, uponReceiving() names the interaction, and withRequest()/willRespondWith() pin the request and the minimal response. MatchersV3.like allows the provider to return other fields, and decimalLike tolerates floating-point precision. executeTest() runs the real consumer code against the mock server, and the resulting JSON is the contract that gets verified later by the provider.
In production
- Each microservice verifies the contracts of every consumer before its release is allowed into production.
- can-i-deploy gates deployments so a provider can only ship when all consumers are verified.
- Teams catch removed fields or renamed endpoints in CI instead of during a midnight incident.
- Contracts become the review artifact in API change approvals, replacing guesswork about who uses what.
Best practices
- Keep contracts consumer-driven: the provider verifies, but the consumer defines the expectation.
- Use flexible matchers for values that vary (ids, timestamps) and exact matches only where the format matters.
- Version consumers and providers, and let the broker record which versions verified against each other.
- Add provider states for preconditions like 'an order with id 42 exists' instead of hard-coding data.
- Make a failed verification a release blocker, not a warning that can be ignored.
Common mistakes
- Trying to use contract tests instead of all functional tests — contracts only prove interface compatibility.
- Matching entire responses exactly, which breaks the moment any unrelated field is added.
- Letting the provider edit consumer contracts without the consumer team reviewing the change.
- Not versioning the contract, so the broker cannot tell which deployments are safe together.
Common issues
Verification fails: expected status 200 but got 404The provider route or base path differs from the consumer's expectation.
Compare the contract's request with the provider's route and fix the mismatch in the consumer contract or provider route.
can-i-deploy returns unknown — no verification foundThe provider never ran verification for that consumer version.
Run the provider's pact:verify task, publish the result to the broker, then re-check can-i-deploy.
The contract does not update when the consumer changesThe pact publish task is missing or runs before the tests finish.
Run the consumer tests first, then publish the generated pacts file with the correct version tag.
Debugging tips
- Read the verification failure report — it shows the exact expectation and the actual response side by side.
- Compare the contract's request path and query against the provider's route table for subtle mismatches.
- Check the Pact Broker matrix to see which consumer and provider versions last verified green.
- Run the provider verification locally with verbose logging to see which expectation triggered the mismatch.
Interview checkpoint
Hands-on lab
- Set up a consumer project with pact-js
- Define a consumer contract for GET /orders/:id
- Generate and publish the pact file to a broker
- Run provider verification and check can-i-deploy
Publish a Contract and Verify It
60 min · advanced
Quick quiz
Who defines the expectations in a consumer-driven contract?
Contract verification requires both consumer and provider services to be running at the same time.
A provider release fails verification against a consumer contract, but nobody uses that endpoint anymore.
Cheat sheet
npm install -D @pact-foundation/pactAdd pact-js to the consumernpm testGenerate the pact files from consumer testsnpx pact-broker publish ./pacts --consumer-app-version <ver>Publish contracts to the brokernpx pact-broker can-i-deploy --pacticipant <app> --version <ver>Gate the deploymentMatchersV3.like(obj)Shape match; extra fields allowedMatchersV3.equal(value)Exact value matchMatchersV3.regex(pattern, example)Pattern match with exampleMatchersV3.decimalLike(x)Tolerate float precision