API Testing
Advanced 16 min read API Testing

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.

contract testingPactconsumer-driven contractsPact Brokerprovider verificationcan-i-deploy
Introduction

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.

Motivation

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.
Think about it

Mental model

Imagine

A signed contract between two teams

Think of

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.

Mechanics

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.

Consumer team
Mock provider
Contract file
Publish to broker
Provider verification
Verified?
Both can release safely
Reference

Key commands

npm install -D @pact-foundation/pact

Install 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.3

Publish the generated contract files to the broker with the consumer version.

npm test -- --testPathPattern=pact

Run only the consumer contract tests to regenerate the pact files.

npx pact-broker can-i-deploy --pacticipant orders-web --version 1.2.3

Ask 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/latest

Download the latest contract JSON directly from the broker for inspection.

Hands-on

Examples

A consumer contract for getting an order

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.

Real world

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.
Guidelines

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.
Watch out

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.
Root cause analysis

Common issues

Error
Verification fails: expected status 200 but got 404
Root cause

The provider route or base path differs from the consumer's expectation.

Fix

Compare the contract's request with the provider's route and fix the mismatch in the consumer contract or provider route.

Error
can-i-deploy returns unknown — no verification found
Root cause

The provider never ran verification for that consumer version.

Fix

Run the provider's pact:verify task, publish the result to the broker, then re-check can-i-deploy.

Error
The contract does not update when the consumer changes
Root cause

The pact publish task is missing or runs before the tests finish.

Fix

Run the consumer tests first, then publish the generated pacts file with the correct version tag.

When things break

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.
Interviews

Interview checkpoint

Practice

Hands-on lab

Publish a Contract and Verify It
60 min · advanced
In progress
Objectives
  • 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

0/5 steps
0%
sandbox · Linux sandbox
$
Try these commands
Test yourself

Quick quiz

0/3
1

Who defines the expectations in a consumer-driven contract?

2

Contract verification requires both consumer and provider services to be running at the same time.

3

A provider release fails verification against a consumer contract, but nobody uses that endpoint anymore.

Quick reference

Cheat sheet

Pact workflow commands
npm install -D @pact-foundation/pactAdd pact-js to the consumer
npm testGenerate the pact files from consumer tests
npx pact-broker publish ./pacts --consumer-app-version <ver>Publish contracts to the broker
npx pact-broker can-i-deploy --pacticipant <app> --version <ver>Gate the deployment
Common matchers
MatchersV3.like(obj)Shape match; extra fields allowed
MatchersV3.equal(value)Exact value match
MatchersV3.regex(pattern, example)Pattern match with example
MatchersV3.decimalLike(x)Tolerate float precision
Go deeper

Official documentation