Test Automation

Consumer-Driven Contract Testing: Verify API Compatibility in CI

Consumer-Driven Contract Testing: Verify API Compatibility in CI

A consumer changes the response field it needs. The provider passes its build, yet the consumer still breaks after deployment because production runs a different provider version. A green job answers a narrower question than whether the versions about to communicate are compatible.

Consumer-driven contract testing makes the consumer's expectations executable. Tests exercise its API client against a controlled provider double and generate an interaction contract. Provider verification then checks whether a particular provider build satisfies those expectations. A version-aware CI gate connects that evidence to the applications already in the target environment.

Consider a fictional OrderWeb consumer and OrderAPI provider. The interactions, version labels, and command examples below are illustrative. They have not been executed against an application, CI system, or Pact Broker.

Table of Contents

Set the Boundary and Prerequisites

Start at the code that constructs requests and translates responses into the consumer's model. A Pact test should exercise that real client, rather than an HTTP call written only inside the test. Otherwise, the generated contract can describe a request that the application never sends.

Keep the claim narrow. Contract verification can check communication expectations without running both complete applications together. It does not establish that production routing, credentials, database configuration, business rules, or a complete user journey work. Retain focused integration checks and critical deployed journeys for those risks. The comparison of integration and end-to-end testing explains how to separate connection evidence from workflow-completion evidence.

Before connecting the pipelines, agree on stable consumer and provider names, ownership of provider-state setup, the Broker connection, and the environment name used by deployment jobs. Supply Broker credentials through the CI secret mechanism, not pact files or committed commands. The documented PACT_BROKER_BASE_URL setting can identify the Broker without embedding its address in each command.

Check the installed consumer and provider libraries' supported Pact specification, plus the Broker and CLI features required for branches, environments, and deployment records. The contract records its specification under metadata.pactSpecification.version; that is different from the application version or npm package version. Older documentation includes historical language-support tables, so confirm capabilities for the packages you actually use rather than assuming every implementation accepts the same configuration.

Generate the Pact Through the Consumer Client

OrderWeb needs to fetch an order and distinguish an existing order from an unavailable one. Define the required behavior before writing an expansive response fixture. For this example, assume the client uses the order identifier and a status value to construct its model, and maps a missing order to a defined not-found result.

  1. Declare an interaction with a provider precondition, the expected request, and an example response. For the successful case, the precondition is that order 42 exists and the request is GET /orders/42.
  2. Configure the actual OrderWeb client to use Pact's mock-server address. Invoke the application's client method and await its result so asynchronous failures reach the test runner.
  3. Assert the model or error outcome that the client produces. Add the missing-order interaction separately, with its own precondition and expected 404 response.
  4. Run the consumer tests in CI and retain the generated pact artifact only after the tests pass. Keep examples deterministic so repeated builds do not create changed contracts merely because a timestamp or identifier was random.

The two illustrative interactions express different expectations even though they use the same endpoint shape:

Interaction

Provider Precondition

Consumer Evidence

GET /orders/42 returns 200

Order 42 exists with an agreed status

The actual client reads the identifier and handles that status

GET /orders/404 returns 404

Order 404 does not exist

The actual client produces its defined not-found outcome

Choose matchers field by field. If the client only needs an identifier to be a string, an appropriate type matcher avoids coupling the contract to one incidental value. If it branches on a status such as approved or pending, a generic string matcher alone is insufficient: it would also accept a value the client cannot handle. Represent the meaningful cases with expectations that protect those branches.

Include the response fields the client needs, not every field returned by today's provider. Conversely, do not loosen an assertion that protects actual parsing or decision logic merely to make verification green. A deterministic example supplies data for the consumer test; the matching rule describes the acceptable provider behavior.

Keep unrelated calculations and UI decisions in their appropriate tests. A contract that receives a valid order response does not prove that a tax calculation, checkout screen, or order-persistence workflow is correct. Use synthetic identifiers and example values throughout, without customer data or production access tokens.

Publish the Consumer Version and Branch

Publish the generated pact to the Broker with the consumer application version and its branch. Use a version that identifies the code being built, normally a commit SHA or a version containing it. The branch identifies a development line; it cannot replace an immutable code identity because its contents change.

For discussion, call the existing consumer version c1 and the proposed version c2, with provider versions p1 and p2. These are fictional labels, not Git hashes. In a working pipeline, map the actual build artifact to the same identity used for contract publication and deployment checks. Do not reuse a convenient constant version across changing builds.

The Broker versions pact content separately from application code. Two consumer builds can publish unchanged expectations, allowing the Broker to reuse a verification for the same contract content against a known provider version. Random fixture data defeats that useful equivalence by making unchanged requirements look different.

A published pact is an expectation awaiting evidence, not approval to deploy. Avoid hand-editing it to match the provider: change the client and tests deliberately, regenerate the artifact, and publish the new code identity. Preserve enough CI evidence to trace the contract back to the build that generated it.

Verify Against the Provider Build

Provider verification replays the contract's requests against a running instance of the provider build under examination. Decide which dependencies remain real and which are controlled, then keep that boundary visible in the test configuration. A verification against p2 does not become evidence about deployed p1 because both builds have the same application name.

Make Provider States Independent

Map each named precondition to provider-owned setup. The existing-order handler can seed an isolated datastore or arrange a controlled repository response; the missing-order handler must ensure its order is absent. In Pact JS, stateHandlers maps the state names to setup behavior, with setup and teardown hooks available when needed.

Each interaction must work independently. Do not rely on a previous interaction creating the order, or on whichever records happen to remain in a shared database. Await setup, reset the data owned by the test, and clean it up without erasing another test's fixtures. Missing or ineffective handlers can leave the scenario different from the one the consumer declared.

Keep the actual provider request handling and response serialization inside the selected boundary. Stubbing an external dependency can be appropriate; replacing the endpoint's entire response with the consumer's expected JSON would bypass the behavior verification is supposed to examine. If persistence itself is the risk, add a separate test with the relevant real database boundary.

Select Consumers and Publish Verification Results

For a provider-change job, configure consumerVersionSelectors intentionally. In the documented Pact JS workflow, mainBranch selects the latest consumer version on its configured main branch, while deployedOrReleased includes currently deployed and currently supported released consumers. Those serve different purposes: developing new expectations and preserving existing integrations.

Add matchingBranch when consumer and provider teams coordinate work using the same feature-branch name. Keep the main-line and deployed-consumer checks alongside it; testing only the feature pair can miss a regression for c1 still running in production. The Broker deduplicates selected pacts by content, so overlapping selectors need not imply repeated verification of identical expectations.

Point providerBaseUrl at the intended test instance. Set providerVersion to the identity of that exact provider build and providerVersionBranch to its branch. Enable publishVerificationResult in controlled CI so results reach the Broker with the right identity. Return or await verifyProvider() through the runner, and propagate failures rather than logging an error and allowing the job to succeed.

Also arrange verification when a consumer publishes changed expectations. Waiting for the provider's next unrelated change leaves the consumer without timely evidence. A Broker-triggered job can verify the changed pact against the relevant provider versions, including the deployed version where required. Keep this targeted pact-URL workflow separate from the ordinary selector-based provider-change job; they answer different scheduling needs.

Gate the Exact Version Before Deployment

Immediately before deploying OrderWeb c2, ask whether that exact version has compatible results with the integrated versions recorded in production. With Broker access already configured, the illustrative command is pact-broker can-i-deploy –pacticipant OrderWeb –version c2 –to-environment production.

Use the command's outcome as a required deployment condition. A positive compatibility result is useful only for the recorded contracts, versions, and environment. An incompatible result blocks the deployment. Missing verification means the evidence is incomplete, not that the pair probably works. If verification is asynchronous, use a bounded wait for the expected result and stop when evidence remains missing; do not convert an unknown result into approval.

Suppose c2 now reads fulfillmentState while c1 still reads status, and p2 is the first provider to return both fields. A successful c2-to-p2 verification cannot authorize c2 while production still runs p1. Verify that p2 also preserves the contract required by deployed c1, gate p2 against that environment, deploy it completely, and record the completed deployment. Then obtain the compatibility decision for c2 against the updated production inventory.

Apply the gate to provider deployments as well as consumer deployments. A provider can break an older deployed consumer even when its own unit tests and the newest feature contract pass. Use an explicit application version, not a global latest lookup that another branch can change between jobs.

Keep dependency discovery and environment records accurate. The Broker cannot infer an unregistered integration or discover what is physically deployed merely by inspecting its contracts. For initial adoption, reconcile known running versions and their contract evidence before treating the gate as a release control. A successful answer is API-compatibility evidence within that declared scope, not complete release readiness.

Record the Completed Deployment or Release

After OrderWeb c2 has successfully completed its rollout, the corresponding illustrative command is pact-broker record-deployment –pacticipant OrderWeb –version c2 –environment production. Record the same version that was checked and deployed, not a newly calculated build identifier.

For replacement deployments, this records the new version and marks the prior deployment as no longer current for the same application, environment, and instance. Call it at the end of the rollout, when the previous instances are gone. Recording p2 too early could permit c2 while requests can still reach incompatible p1 instances during a rolling deployment. Coordinate deployments so the compatibility decision's assumptions remain true.

If a rollback restores an older build, record that restored version after the rollback completes. Otherwise, subsequent checks can reason about an environment that no longer exists.

Mobile applications and distributed libraries need different lifecycle semantics: releasing a new version does not remove older versions from users. Use record-release for that model and record-support-ended when support for a released version genuinely ends. Do not mark older clients unsupported merely to make a provider gate pass. Provider selection must continue to cover the released versions the team still supports.

Diagnose Green CI Without Compatibility Evidence

When a deployment is blocked despite green jobs, inspect the missing evidence before rerunning everything:

  • Confirm the consumer test invoked the actual client and generated the pact associated with the version being deployed.
  • Check that provider states established the declared preconditions and verification exercised the intended provider build.
  • Check that the result was published under that provider's actual version and branch, rather than remaining only in a local test report.
  • Inspect selected consumer versions. Feature-branch compatibility does not substitute for deployed or supported released consumers.
  • Review pending-pact behavior. It can let the provider verification task succeed while a new contract's failed verification remains recorded in the Broker; the consumer's compatibility gate must still block that unsupported expectation.
  • Compare deployment records with the running environment, including completed rollbacks and supported releases.

Preserve the failed interaction, contract identity, application versions, and target environment with the CI record. Investigate whether the issue is a genuine incompatible change, missing verification, or incorrect bookkeeping. Retrying without identifying that distinction can hide the reason the gate exists.

Start with one consumer-provider pair whose client, states, build identities, and deployment records are explainable. Expand the contract suite when another dependency needs evidence. Keep the deployment decision tied to the exact versions that will communicate, and retain complementary tests for behavior outside that contract.

Related

More on Test Automation

Create traceable test cases, prepare release-specific test runs, and keep approved coverage current as the product changes.