Pact Contract Testing

Pact contract testing checks whether a provider satisfies the interactions that its consumers actually depend on. Start with one consumer call, exercise the client against Pact's mock server, and verify the resulting contract against the provider. A mock returning the answer you invented is only half the conversation.

The useful outcome is a version-specific compatibility result. It does not establish that the entire product works, that production credentials are correct, or that a service can survive its peak load. Keep those questions in their own tests.

TL;DR

Quick Example

This fictional Node.js example uses @pact-foundation/pact 17.1.4. In a disposable directory, install that exact dependency and save the following as example.cjs. Run it with Node 24. It creates a temporary contract directory and loopback servers, verifies a tiny provider, then removes the temporary files. No Broker or deployed service is contacted.

The exact identifier stays 7; the name may change while remaining a string. Replacing the provider's name with a number should fail verification. The consumer here is deliberately tiny: in an application, call the real HTTP client or adapter instead of duplicating its request logic inside the test. The Pact JavaScript consumer guide explains the generated-contract workflow and current Pact interface.

Core Concepts

Consumer expectations become an artifact

The consumer test describes an interaction and checks its own code against the mock. The recorded artifact carries that expectation to the provider pipeline. It should represent behavior the consumer requires, rather than every response field the provider happens to expose today. Otherwise harmless additions or fixture changes create busywork that teaches teams to ignore failures.

Provider verification closes the loop

Verification replays the contract's requests against a running provider and evaluates its responses. A passing consumer test alone cannot establish provider compatibility. The provider verification guide documents the verifier configuration and application setup responsibilities. Keep the tested provider version identifiable: “the API passed yesterday” is not an artifact you can release.

Provider states describe preconditions

A state such as “product 7 exists” tells the provider what must be arranged before an interaction. It is not an instruction to call another consumer test first. Set up minimal data, reset it between interactions, and avoid depending on execution order. Pact's provider-state guidance treats these as isolated preconditions. Our stateless fixture needs no state handler; a database-backed provider usually does.

From Local Checks to a Deployment Gate

For a proposed pipeline, publish contracts with immutable consumer versions, verify the relevant provider versions, and publish their results. Before deployment, query compatibility for the exact candidate version and target environment. Record successful deployment afterward. Pact's can-i-deploy documentation explains how verification results and environment deployment records form that decision.

Treat missing evidence as a distinct condition. An absent result is not a pass; neither is a successful result for an unrelated branch. Protect Broker credentials as pipeline secrets and give publishing jobs only their required permissions. Establish the initial baseline explicitly when onboarding a new provider rather than disabling the gate indefinitely.

An important counterexample is an authorization defect. A provider can return the correct response shape while accepting an unauthorized caller. Unless the contract scenario exercises the relevant denial behavior, the happy-path result says nothing about it. Add focused security and integration tests with appropriate isolation.

Pact and OpenAPI Answer Different Questions

Consumer-driven Pact contracts capture exercised expectations from clients. An OpenAPI description provides an API description that tooling can validate and use across many operations. A schema-valid response can still break a consumer that depends on a particular interaction; a small passing consumer contract can leave most of a provider's documented API untested.

Choose the combination from the problem. If teams need proof that independently released clients remain compatible, begin with their important interactions. If the immediate gap is an incomplete or inconsistent public API description, improve the specification and its validation. A team may need both. Avoid counting two checks as interchangeable simply because both mention JSON.

Best Practices

Make failure informative

Include a deliberate incompatible response in the teaching fixture or test harness. If changing name to a number still passes, inspect the matcher, loaded artifact, and tested provider before expanding coverage. This inexpensive negative control checks that the test can detect the defect it claims to prevent.

Keep contracts near real consumer code

Exercise request construction and response handling through the application boundary you own. A handwritten parallel client can pass while the production adapter sends a different header or parses another field. Start with a small valuable path and add observed dependencies rather than speculative assertions.

Keep provider setup bounded

Use disposable data and explicit state handlers. Never point a state-setup routine at production to make a test convenient. Document what the fixture creates, how it is cleaned up, and which external systems are replaced. Repeatability makes a failed contract actionable across teams.

Common Mistakes

Bad: Publish a contract after testing only a mocked application client.

Correct: Call the real consumer boundary against Pact's mock and verify the artifact against the provider.

Bad: Match an entire response exactly because the recorded example contains it.

Correct: Assert the fields and values the consumer needs, with matchers that preserve meaningful incompatibilities.

Bad: Let a compatibility check use an unspecified latest version.

Correct: Tie results to immutable builds and query the intended environment's recorded versions.

FAQ

Does Pact replace integration tests?

No. Use it for declared interface compatibility and retain tests for wiring, credentials, persistence, and business workflows. A green contract does not demonstrate a working production network path.

How is this different from OpenAPI-based testing?

The starting evidence differs: exercised consumer expectations versus an API description. Inspect which behavior each check covers before deciding whether one leaves a material gap.

Should every response field be included?

Include what the consumer relies on. Add a field when its absence or changed type would break that consumer, rather than turning an incidental fixture into a permanent promise.

What should block a deployment?

For the proposed policy, incompatible or missing required results block the automated release until resolved. Agree the baseline, ownership, and controlled exception procedure before enforcing that policy across teams.

Related Topics

References