Contract Testing Guide for Safer API Changes

Build consumer driven Pact contract testing with real API adapters, isolated provider states, precise matching, and deployment gates for supported versions.

Conceptual white consumer and provider cards connected by a contract check representing API compatibility

An API can satisfy its published schema while breaking the client that actually calls it.

Contract testing checks whether independently changing applications agree on the interactions they exchange. For HTTP APIs, that means establishing what a consumer sends and which response details its code needs. A compatible schema is useful, but it does not establish that a particular client constructs the right request or handles the response correctly.

This guide focuses on consumer driven contract testing with Pact. It connects consumer tests, provider verification, and release decisions without treating compatibility as proof of complete product correctness. Every scenario below is hypothetical. The examples describe intended checks, not experiments, measured outcomes, or production incidents.

Define consumer driven contract testing with Pact

Separate API compatibility from business correctness

Pact captures interactions from consumer tests and verifies those expectations against the provider. The HTTP consumer testing process describes testing with a mock, generating a contract, sharing it, and replaying requests against a running provider. Both sides can run independently without assembling the entire system.

Choose a boundary with separate ownership or release schedules. In a hypothetical booking product, a mobile availability client and its reservation API qualify. Start with the requests that client actually makes, including error responses it handles differently. A catalog of every possible provider endpoint creates obligations unrelated to this consumer.

OpenAPI schema validation checks an API description and associated constraints. Consumer contracts add evidence about actual client dependencies. Neither automatically proves inventory accounting, authorization policy, or successful payment settlement. Pact's contract versus functional testing guidance explains why provider business rules need their own tests. Keep functional and integration coverage where those rules and dependencies can be exercised meaningfully.

Build a repeatable contract testing workflow

Contract testing workflow labeled Define expectations, Verify provider, and Gate releases
Define expectations through consumer code, verify the provider, and gate releases against the versions that remain relevant.

Connect expectations to verification and release evidence

Define expectations through the real consumer adapter

Configure the Pact mock with one interaction, then point the application's real API adapter at its base URL. Invoke the adapter method, await its result, and assert the returned domain value or expected error. Let Pact verify the outgoing interaction before publishing the generated contract. The consumer test guidance specifically calls for executing actual client code and checking its handling of responses.

In a hypothetical reservation client, the test calls the production adapter's fetchAvailability method. It asserts the parsed availability result, including the identifier and availability state the adapter returns. This exercises URL construction, headers, decoding, and mapping. Calling a generic HTTP library directly from the test bypasses that mapping; asserting only that the mock returned its configured body proves too little.

Verify the provider using the generated contract

Start the provider build being evaluated, establish each interaction's preconditions, and replay the contract through its real HTTP routing and serialization. Associate each published verification outcome with the exact provider build and repository branch so later deployment checks can select the intended evidence. The Pact JavaScript provider reference documents verifier configuration, state handlers, broker retrieval, and result publication.

Stub downstream dependencies deliberately while retaining the API boundary under review. Record those substitutions in the verification configuration. A response produced entirely by a replacement handler would bypass the provider behavior you intended to protect. A real handler with a controlled downstream service can establish compatibility without claiming that downstream service works in production.

Gate releases against relevant application versions

Publish contracts after successful consumer tests, publish provider verification results from CI, and check compatibility before deploying either application. After successful deployment, record the deployed version. Treat these as separate pipeline steps with observable failures. A local verification without published results cannot supply the broker evidence required by another repository's release gate.

Assign ownership before adding the gate. Consumer engineers maintain client expectations; provider engineers maintain state setup and verification. The release pipeline owns version identity and deployment records. When a contract changes, notify the responsible provider team through the established CI workflow so a missing result has a clear route to resolution.

Match the API contract to real consumer dependencies

Keep requests precise and responses intentionally tolerant

Request expectations should constrain method, path, query parameters, headers, and body fields that the adapter must send. Response expectations should constrain the data it consumes while allowing unrelated additions. Pact's matching gotchas describe stricter request matching and tolerance for unexpected response JSON keys. Extra request headers are tolerated, so explicitly require headers that affect provider behavior. These principles depend on the applicable matching rules and body format; they are not permission to accept every response.

In a hypothetical availability API, a new unused response field should ordinarily remain compatible. Renaming the identifier the adapter reads should fail verification. If the actual decoder rejects unknown keys, Pact's default tolerance does not prove that decoder tolerates additions. Add a consumer robustness test for that behavior or change the decoder policy intentionally.

Distinguish types from meaningful values

Use type matching for a generated identifier when its exact value is irrelevant. Use an exact value or constrained matcher when the consumer branches on a status or parses a specific format. The matching documentation explains example values, regular expressions, and type matching. A string matcher cannot establish that every returned string is a supported business state.

Cover the status codes and headers that change client behavior. Distinguish a missing field, explicit null, an empty value, and a populated value when decoding differs. Define separate interactions where necessary and inspect matcher scope and inheritance. Omitting a response field from a contract does not require its absence from provider responses; test absence sensitive decoding separately. A broad object matcher can loosen nested expectations unintentionally. Keep exact constraints on values that choose a consumer code path.

In a hypothetical reservation response, available and unavailable might both be strings yet trigger different adapter results. Test both relevant outcomes. Separately test business correctness of the availability calculation in the provider. The contract establishes the agreed representation, while functional tests establish how that representation is determined.

Make provider states isolated and repeatable

Build fixtures as independent preconditions

Provider states describe what must already be true before an interaction. The provider states documentation requires interactions to be verified independently without relying on previous interaction results. Give states meaningful names and parameters, such as an available reservation slot with a known identifier.

Implement setup idempotently in an isolated test environment. Reset relevant records or upsert deterministic synthetic fixtures, control clocks where time matters, and clean up independently of test ordering. Parallel workers need separate databases or fixture namespaces. Do not prepare state by modifying production records, calling live payment systems, or reusing customer credentials.

In a hypothetical cancellation contract, the canceled reservation must exist before the interaction starts. It cannot depend on an earlier create request in the same pact. A create followed by cancellation belongs in a separate functional journey test. State handlers should fail clearly when setup is incomplete so a fixture problem cannot masquerade as successful compatibility verification.

Select Pact contracts that protect independent releases

Preserve baselines branches and active environments

Verify the latest consumer contract from its configured main branch as a development baseline, matching feature branches when appropriate, and versions currently deployed or released and supported. The recommended verification configuration uses mainBranch, matchingBranch, and deployedOrReleased selectors together. Latest development alone does not protect older clients still in use.

Check that each consumer's main branch property is correct and that publication includes branch metadata. The consumer version selector reference explains selector behavior and environment filtering. Inspect the selected contracts in CI output. An empty selection or accidentally narrow environment filter is a coverage problem, even if the verification process exits successfully.

Use immutable application identities tied to source commits for both consumer publication and provider results. The version number guidance distinguishes application versions from broker managed contract content versions. Never reuse a version identifier for changed code. Independent applications need independent version identities, not a shared release number that conceals which pair was verified.

The branch documentation describes first class repository branch metadata. Prefer branches and environment deployment records over legacy tags for modern workflows. A tag named production is not sufficient evidence that the broker's current environment model matches the applications actually serving traffic.

Use pending and WIP contracts for controlled evolution

Pending contracts allow a provider pipeline to report an unsupported new consumer expectation without immediately blocking that provider's main build. The verification failure remains recorded, and the consumer cannot deploy on that evidence. See the pending contracts documentation. Pending status depends on broker history and provider branch context; it is not a blanket exception for arbitrary failures.

Work in progress contracts extend discovery to eligible pending contracts outside explicit selectors. Configure the inclusion date deliberately and publish accurate branch information. This gives consumers feedback on evolving expectations while preserving established compatibility requirements. Do not replace failing verification commands with unconditional success or reinterpret an established regression as acceptable merely because pending support is enabled.

Automate contract verification with a deployment gate

Treat unverified pairs as unknown

The broker matrix links consumer versions, provider versions, contract content, and verification results. Run can-i-deploy for the exact application version and target environment before deployment. The deployment safety documentation explains checking against integrated versions already present there. Enforce the command's result in CI rather than displaying it as an informational report.

A required pair with no successful verification is unknown, not compatible. Fail the gate while results are missing or failed. Bounded polling can wait for verification triggered by publication; expiration must block deployment. Diagnose missing publications, incorrect selectors, version mismatches, and stale environment records before changing policy. The matrix only covers relationships represented by contracts and accurate metadata.

Keep older mobile releases relevant

Use record-deployment when a successful deployment replaces the previous version. Use record-release when multiple artifact versions remain available, as with mobile apps. The deployment and release lifecycle reference explains that recording a release preserves earlier released versions. Record deployment after the rollout succeeds and replacement is complete, not when rollout starts.

In a hypothetical mobile rollout, publishing a new client does not upgrade every user's installed app. Retain older contracts and supported release records until the actual support policy permits retirement. Mark support ended deliberately when appropriate, and align broker retention with that policy. Deleting old evidence to obtain a green gate can conceal the exact compatibility risk the workflow should expose.

Prove the gate can detect a breaking change

Use a bounded negative control

In a hypothetical isolated validation exercise, rename a response field required by the existing consumer. Verify that the provider check fails, the failure is published, and deployment is blocked for the affected pair. Revert the intentional change afterward. Also test a missing verification result so the gate demonstrates how it handles uncertainty.

For an intended migration, first add the new representation while preserving the old one. Release updated consumers, verify both generations, and remove the old representation only after relevant consumers have migrated or support has ended. Keep rollback compatibility in the decision. The feature flag testing guide helps cover configuration states during a staged transition.

Keep transport and user experience coverage alongside contracts

Pact also supports asynchronous message contracts, but these verify message expectations rather than the entire delivery system. The message testing documentation describes abstracting transport to test message handling. The Pact Broker stores contracts and verification evidence; it is distinct from a Kafka or RabbitMQ message broker. Separate integration tests must cover routing, retries, ordering, and delivery behavior.

Use the Playwright end to end testing guide for complete browser journeys and the webhook testing guide for delivery and recovery risks. Contract evidence does not guarantee an accessible interface, understandable failure message, or successful journey across every dependency.

Create a BetaRoast feedback round with safe accounts and a specific user mission. Ask people whether availability changes, recovery messages, and retry actions are understandable. BetaRoast provides human feedback on the product experience. It does not generate Pact contracts, execute provider verification, or automate deployment gates.

Frequently asked questions

What is contract testing used for?

It checks compatibility at an application boundary. Consumer driven Pact tests express actual client expectations, provider verification checks those expectations, and broker evidence supports release decisions for relevant versions.

Is OpenAPI validation enough for consumer compatibility?

It supplies useful schema evidence, but does not establish that a specific consumer builds requests and handles responses correctly. Exercise the real adapter and retain separate functional coverage.

Can pending contracts bypass a failed deployment gate?

No. Pending contracts can avoid blocking a provider build on new expectations while preserving failed verification evidence. The affected consumer still needs successful compatibility evidence before deployment.

Should providers verify only the latest mobile contract?

No. Older supported clients can remain installed after a new release. Verify relevant released versions and preserve their contracts until support ends under an explicit policy.

THE BOTTOM LINE

Useful contract testing exercises the real consumer adapter, verifies its expectations against the provider, and gates each release using evidence for relevant application versions. Keep fixtures isolated, match only genuine dependencies, and retain contracts for supported mobile clients. Pair compatibility evidence with functional tests and human feedback through BetaRoast.

PUT IT INTO PRACTICE

Make your next beta count.

Define the mission, recruit matching testers, and turn feedback from real devices into a better release.

Start a Roast

Technical guidance can change. Verify current official documentation for your platform and tools before release.