Browse the Knowledge Hub211 resources

Pact review: contract testing tutorial and pricing
Pact is an open-source framework for consumer-driven contract testing. A consumer’s unit tests run against a Pact mock server and record what they need from a provider as a contract file; the provider then replays that contract against its real implementation. A Pact Broker or the commercial PactFlow stores contracts and results, so each team can check compatibility before it deploys.
QAble has evaluated this tool and works alongside it. The factual sections are complete; our view is bounded to what we have actually seen rather than a long-term verdict.
- last reviewed
- 13 October 2026
- version covered
- Pact Specification V4 (Pact JS 17, Pact JVM 4.7)
- licence
- Open source
At a glance
Pact at a glance
- Vendor
- PactFlow / SmartBear
- Category
- Mocking & virtualisation
- Licence
- Open source (MIT for most libraries and the Broker; Pact JVM is Apache 2.0. PactFlow is commercial)
- Platforms
- JVM, Node.js, .NET, Python, Go
- Languages
- JavaScript, TypeScript, Java, Kotlin, Scala, C#, Go, Python, Ruby, Rust, PHP, Swift, C++
- Integrates with
- Jenkins, GitHub Actions, PactFlow
- First released
- 2013
- Version reviewed
- Pact Specification V4 (Pact JS 17, Pact JVM 4.7)
Verdict
Our verdict on Pact
Strengths
- Catches breaking API changes in the provider’s own build, before anything is deployed together
- Consumer tests run against a local mock server, so they are fast and need no shared environment
- can-i-deploy turns the contract matrix into a yes or no answer for each release
- Implementations for JavaScript, JVM, .NET, Go, Python, Ruby, Rust, PHP and more share one contract format
- Pending and work-in-progress pacts let consumers publish changes without breaking the provider’s build
- Open-source framework and Broker; PactFlow is optional
Weaknesses
- Needs buy-in from both consumer and provider teams, or it delivers nothing
- Provider states mean writing and maintaining test data setup for every interaction
- Not suited to public APIs whose consumers you do not know or control
- Breaking renames in recent majors, such as Matchers in Pact JS 16, confuse older tutorials
- Bearer-token auth and bi-directional testing are PactFlow features; the open-source Broker uses basic auth
Our take
Pact is the right answer when several teams deploy services independently and the only alternative is a shared end-to-end environment that everyone waits on. It moves integration checks into each team’s own build and answers one specific question, whether this version can be deployed next to what is already there, without deploying anything together. QAble has evaluated it rather than maintained a long-running contract suite, so treat this as a bounded take. Expect the cost to be organisational more than technical: both teams must adopt it, provider states need real engineering, and the payoff only arrives once can-i-deploy gates releases. It verifies the agreement, not the behaviour, so it complements end-to-end tests rather than replacing them. For a public API with unknown consumers, schema-based testing is usually a better fit.
We have evaluated this tool rather than maintained a long-running suite on it, so this is a bounded view rather than a long-term verdict. The rest of this page is factual.
How it works
What is Pact and how does it work?
The consumer test writes the contract
In a consumer test you describe an interaction: given a provider state, upon receiving a request, the provider will respond with a certain response. Pact starts a mock server on a local port, your real API client calls it, and the mock checks the request and returns the response you described. If the test passes, Pact writes the interaction into a JSON pact file named after the consumer and provider. Because only interactions the consumer’s code actually exercises end up in the file, the contract records what the consumer uses, not everything the provider offers. That is what consumer-driven means, and it is why providers can change unused fields freely.
The provider replays it against real code
Provider verification takes the pact files, fetches them from a Broker or reads them locally, and replays each request against a running instance of the real provider. Before each interaction it calls a provider state handler, code you write that puts the provider into the state the interaction names, such as a payment that exists or a user who is not authenticated. The verifier then compares the real response with the contract using its matching rules. Each side runs in its own pipeline, at its own pace, and neither needs the other deployed. The cost is that state handlers are real test code that must keep pace with the provider’s data model.
Matching rules instead of exact values
A contract that demanded exact values would break whenever test data changed. Matchers record the shape instead: like says any value of the same type, eachLike says an array whose items look like this one, and integer, string, regex and datetime constrain specific fields. The example values are what the mock returns to the consumer; the rules are what the provider is checked against. Over-specifying is the classic mistake, because asserting exact values or fields the consumer never reads turns a contract test into a brittle functional test of the provider.
The Broker and the deployment matrix
The Pact Broker stores every published pact and verification result against application versions, branches and environments. From that it builds a matrix of which consumer versions have been verified against which provider versions. can-i-deploy queries the matrix before a release: given this version and the target environment, have all the integrations it depends on been verified against what is deployed there? record-deployment then tells the Broker what was released. Webhooks let a changed pact trigger the provider’s verification build. The Broker is open source; PactFlow is the hosted commercial version from SmartBear with extra features.
Strengths
What it does well
Shared contract format across languages
The Pact Specification, now at V4, defines the contract format, so a JavaScript consumer and a Java provider exchange the same files. Most implementations, including Pact JS, Pact Go, Pact .NET, Pact Python and Pact Ruby, wrap a shared Rust core through a native interface, which keeps matching behaviour consistent between languages.
Message pacts for event-driven systems
Beyond HTTP, Pact can describe asynchronous messages, such as events on a queue, and synchronous message interactions. The consumer test checks that its handler can process the message, and the provider test checks that the code producing it builds a matching message. This covers Kafka or SNS-style integrations without running a broker in the test.
Gating releases with can-i-deploy
A single CLI command asks the Broker whether a version is safe to deploy to an environment, and fails the pipeline if not. It can poll while a provider verification is still running, using retry options, and it is the step that turns contract tests from documentation into an actual safety check on releases.
Pending and work-in-progress pacts
With pending pacts enabled, a new or changed contract that the provider has never passed is still verified, and the result reported to the Broker, but it does not fail the provider’s build. Work-in-progress pacts extend this to contracts from feature branches. Together they let consumers propose changes without blocking the provider team.
Plugins for other protocols and formats
The V4 specification added a plugin mechanism, so support for protocols and content types such as gRPC and Protobuf can be added without changing the core. Plugins are installed with the pact-plugin-cli tool and are versioned separately from the language libraries.
Bi-directional contracts in PactFlow
PactFlow, the commercial product, adds bi-directional contract testing: a provider publishes an OpenAPI description and its own test evidence, consumers publish pacts or mocks, and PactFlow checks the two for compatibility. It suits providers who will not write provider states, at the cost of a weaker guarantee than replaying requests against real code.
Pricing
Pact pricing and licensing
The Pact libraries, the Pact Broker and the Rust command-line tools are free and open source, mostly under MIT, with Pact JVM under Apache 2.0. Running your own Broker means hosting a web application and a database. PactFlow, from SmartBear, is the commercial alternative: a hosted Broker with team management, SSO and bi-directional contract testing. Its pricing page lists a free Starter plan, a Team plan priced per block of users, and Enterprise on quote.
- Licence: MIT for Pact JS, Pact Go, Pact .NET, Pact Python, the Rust core and the Broker; Apache 2.0 for Pact JVM and Pact PHP.
- Self-hosted Broker: free, distributed as a Docker image (pactfoundation/pact-broker); you pay for hosting, backups and upgrades. It supports basic auth, not bearer tokens.
- PactFlow Starter: free, with unlimited users and 2 integrations, which is enough for a proof of concept between two services.
- PactFlow Team: 10 or 25 users and 50 integrations. List price on pactflow.io on 13 October 2026 for 10 users was $127 USD a month billed monthly, or $1,385 a year billed annually.
- PactFlow Enterprise: custom quote, unlimited integrations, SAML SSO, role-based access and a SaaS or on-premises option.
- Hidden cost: engineering time on both sides, chiefly provider state handlers and pipeline changes for publishing, verification and can-i-deploy.
Checked against the vendor’s own pages on 13 October 2026. Pricing changes; confirm on the official site before buying.
Getting started
Pact tutorial: getting started
Setup, step by step
- Agree consumer and provider names with the other team; they key everything in the Broker.
- Install Pact JS as a dev dependency; version 17 needs Node.js 22 or later.
- Write a consumer test that sends your real API client to the Pact mock server.
- Run it and inspect the generated pact file in the pacts folder.
- Hand the file to the provider team, or publish it to a Broker, for provider verification.
- Add provider state handlers until every interaction verifies against the running provider.
Install
npm install --save-dev @pact-foundation/pact
# Pact JS 17 requires Node.js 22 or laterA first example
// test/payments.pact.test.ts (Jest)
import path from 'node:path'
import { Pact, Matchers, SpecificationVersion } from '@pact-foundation/pact'
import { PaymentsClient } from '../src/payments-client'
// Since Pact JS 16, Pact is the V4 DSL and Matchers are the V3 matchers
const { integer, string, like } = Matchers
const provider = new Pact({
consumer: 'checkout-web',
provider: 'payments-api',
dir: path.resolve(process.cwd(), 'pacts'),
spec: SpecificationVersion.SPECIFICATION_VERSION_V4,
})
describe('PaymentsClient', () => {
it('fetches an authorised payment', async () => {
await provider
.addInteraction()
.given('payment 42 is authorised')
.uponReceiving('a request for payment 42')
.withRequest('GET', '/payments/42', (builder) => {
builder.headers({ Accept: 'application/json' })
})
.willRespondWith(200, (builder) => {
builder.headers({ 'Content-Type': 'application/json' })
// Types are checked on the provider side, not these exact values
builder.jsonBody({ id: integer(42), status: string('AUTHORISED'), amount: like(1999) })
})
.executeTest(async (mockserver) => {
// Exercise the real client, pointed at the mock server
const payment = await new PaymentsClient(mockserver.url).getPayment(42)
expect(payment.status).toBe('AUTHORISED')
})
})
})What trips people up first
Testing the mock instead of your code. The first consumer test people write often sends a raw HTTP request to the mock server and asserts on the response, which proves nothing: the mock returns exactly what you told it to. The test must call your real API client, the code that will call the provider in production, and assert on what that client returns to the rest of the application. The second hurdle is stale interactions. Clear the pacts folder before a run, so interactions you deleted from the tests do not linger in the file and fail on the provider side.
In practice
Using Pact in practice
Writing contracts that do not break for the wrong reasons
Put Pact tests next to the API client they cover, one file per provider, and include only the requests the consumer really makes and only the response fields it really reads. Use type matchers for values and regex or datetime matchers for formats, and keep exact values for things the consumer genuinely depends on, such as an enum it switches on. Name provider states as facts about data, such as payment 42 is authorised, not as steps, so the provider team can implement them once and reuse them. Prefer one interaction per meaningful response, including the error responses your client handles, because untested error shapes are where contract surprises tend to sit.
Verifying the provider against the Broker
The provider’s verification test starts the service, or a test instance of it with external dependencies stubbed, and points the Verifier at the Broker. The consumer version selectors recommended by the Pact docs fetch the main branch of each consumer, every version currently deployed or released, and any consumer branch with the same name as the provider branch. Enable pending pacts so a consumer’s new expectation does not break the provider build, and publish verification results only from CI, with the git commit as the provider version and the branch name set, which can-i-deploy relies on.
// test/pact.provider.test.ts (Jest), run with the provider listening on port 8080
import { Verifier } from '@pact-foundation/pact'
import { seedPayment } from './support/db'
describe('payments-api', () => {
it('meets its consumers’ contracts', () => {
return new Verifier({
provider: 'payments-api',
providerBaseUrl: 'http://localhost:8080',
pactBrokerUrl: process.env.PACT_BROKER_BASE_URL,
pactBrokerToken: process.env.PACT_BROKER_TOKEN,
// Recommended selectors from docs.pact.io
consumerVersionSelectors: [
{ mainBranch: true },
{ deployedOrReleased: true },
{ matchingBranch: true },
],
enablePending: true,
publishVerificationResult: process.env.CI === 'true',
providerVersion: process.env.GIT_COMMIT,
providerVersionBranch: process.env.GIT_BRANCH,
stateHandlers: {
// Names must match the consumer's given(...) text exactly
'payment 42 is authorised': async () => {
await seedPayment({ id: 42, status: 'AUTHORISED' })
},
},
}).verifyProvider()
})
})Publishing and gating in GitHub Actions
On the consumer side the pipeline has three jobs to do: run the Pact tests, publish the pact files with the commit as the version and the branch name, and, before deploying, ask can-i-deploy. The Rust pact-broker-cli is the current command-line client and has an official GitHub Action that installs it, pinned through the action ref. With PactFlow, authenticate with an API token in PACT_BROKER_TOKEN; the open-source Broker takes PACT_BROKER_USERNAME and PACT_BROKER_PASSWORD instead. After a successful deployment, call record-deployment so the Broker knows what is in each environment, otherwise deployedOrReleased selectors and can-i-deploy have nothing to compare against.
# .github/workflows/contract.yml (consumer: checkout-web)
name: contract
on: [push, pull_request]
jobs:
pact:
runs-on: ubuntu-latest
env:
PACT_BROKER_BASE_URL: ${{ vars.PACT_BROKER_BASE_URL }}
PACT_BROKER_TOKEN: ${{ secrets.PACT_BROKER_TOKEN }}
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
cache: npm
- run: npm ci
- run: rm -rf pacts && npx jest pact
- uses: pact-foundation/[email protected]
- name: Publish pacts
run: >
pact-broker-cli publish ./pacts
--consumer-app-version ${{ github.sha }}
--branch ${{ github.head_ref || github.ref_name }}
- name: Can this version go to production?
if: github.ref == 'refs/heads/main'
run: >
pact-broker-cli can-i-deploy --pacticipant checkout-web
--version ${{ github.sha }} --to-environment production
--retry-while-unknown 12 --retry-interval 10Rolling it out across teams
Start with one consumer and one provider that change often and break each other, not with the whole estate. Get the full loop working there, consumer test, publish, provider verification, can-i-deploy and record-deployment, before adding more integrations, because a half-built loop gives a false sense of safety. Create the environments in the Broker first, since can-i-deploy and record-deployment refer to them by name. Set up a Broker webhook so a changed pact triggers the provider’s verification build; without it, the consumer waits for the provider’s next unrelated commit. Agree who owns a failing verification: the consumer who changed an expectation, or the provider who changed behaviour.
Where contract tests stop and other tests start
Pact checks that requests and responses have the shape both sides expect for the interactions the consumer exercises. It does not check business rules in the provider, performance, or what happens across three services in a real environment. Keep functional tests in the provider’s own suite, keep a thin layer of end-to-end journeys for the paths that matter most, and use a mock server such as WireMock for failure cases like timeouts that a contract does not describe. Contract tests make that end-to-end layer smaller and less flaky; they rarely remove it entirely.
Pitfalls
Common pitfalls
Copying pre-16 Pact JS examples
Why it happens: Pact JS 16 renamed the exports: Pact and Matchers now mean the V4 DSL and the V3 matchers, and the old ones became PactV2 and MatchersV2. Older tutorials using Pact with addInteraction objects, somethingLike or term no longer compile as written.
The fix: Use Pact with the builder API and Matchers such as like, eachLike, integer, string and regex. If an old suite must keep working, import PactV2 and MatchersV2 explicitly while migrating.
Over-specified contracts
Why it happens: Asserting exact values, every response field or headers the client ignores makes the provider fail verification over changes no consumer would notice, and teams start to resent the tool.
The fix: Match on type and format, include only fields the consumer reads, and review contracts in code review the way you would an API change.
Provider states that drift or leak
Why it happens: State handlers set up data for each interaction. If they depend on each other, share a database without cleanup, or no longer reflect the real data model, verification passes or fails for reasons unrelated to the contract.
The fix: Make each handler idempotent and self-contained, use setup and teardown handlers where needed, and stub the provider’s own downstream dependencies during verification.
can-i-deploy that always says yes, or always no
Why it happens: Without branches, versions tied to commits and record-deployment, the Broker cannot know what is in each environment, so the matrix is empty or compares the wrong versions.
The fix: Publish with the git commit and branch, create environments in the Broker, call record-deployment after every release, and check the matrix in the Broker UI.
Using Pact for a public API
Why it happens: Consumer-driven contracts need consumers who write tests and publish them. A public API has unknown consumers, so the contract set is always incomplete.
The fix: Use schema-based approaches such as OpenAPI validation for public APIs, or PactFlow’s bi-directional testing where some consumers can publish and others cannot.
Node.js and CLI changes catching CI out
Why it happens: Pact JS 17 dropped Node.js 20 and requires 22 or later, and since Pact JS 13 the command-line tools moved out of the main package into @pact-foundation/pact-cli, with the Rust pact-broker-cli now the current client.
The fix: Pin a supported Node.js version in CI and call broker commands through pact-broker-cli or its GitHub Action rather than scripts inherited from older setups.
Compared
How Pact compares
| Tool | What it checks | Who defines the contract | Languages | Checks the real provider | Licence |
|---|---|---|---|---|---|
| Pact | Request and response shapes per interaction | Consumer tests generate it | JS, JVM, .NET, Go, Python, Ruby, more | Yes, replayed against the running provider | MIT; Pact JVM Apache 2.0 |
| Spring Cloud Contract | Contracts for HTTP and messaging | Contracts written by hand, verified by producer | JVM and Spring focused | Yes, generated producer tests | Apache 2.0; moved to Stubborn.sh in 2026 |
| WireMock | Nothing; it serves stubs you define | Whoever writes the stubs | Java DSL or JSON over HTTP | No | Apache 2.0 |
| Mountebank | Nothing; it serves imposters you define | Whoever writes the imposters | Any, through a REST API | No | MIT |
FAQ
Frequently asked questions
Is Pact free?
Yes. The Pact libraries, the Pact Broker and the command-line tools are open source, mostly under MIT, with Pact JVM under Apache 2.0, and you can self-host the Broker from its Docker image. PactFlow, SmartBear’s hosted Broker, is the paid option: it has a free Starter plan with two integrations, a Team plan, and Enterprise pricing on request.
What is the difference between Pact and PactFlow?
Pact is the open-source framework: the libraries that write and verify contracts, the specification, and the self-hosted Pact Broker. PactFlow is a commercial product from SmartBear built on the Broker, adding hosting, team and permission management, SSO, bearer-token authentication, and bi-directional contract testing against OpenAPI descriptions. You can use Pact fully without PactFlow.
Does Pact replace end-to-end tests?
It replaces many of them, not all. Pact verifies that each pair of services agrees on the requests and responses the consumer uses, which catches most integration breakage early and without a shared environment. It does not test business behaviour across several services, performance, or configuration in a real environment, so keep a small set of end-to-end journeys for the most important paths.
Pact vs Spring Cloud Contract: which should I use?
Pact is consumer-driven and polyglot, with contracts generated from consumer tests and a Broker for cross-team workflows. Spring Cloud Contract is JVM-focused, with contracts written by hand and verified by generated producer tests. Note that Spring announced on 6 July 2026 that Spring Cloud Contract is moving to Stubborn.sh and will be removed from future Spring Cloud release trains, which matters for a new adoption.
Can Pact test GraphQL, gRPC or Kafka?
GraphQL, yes: Pact JS has GraphQL support in its V4 DSL, since a GraphQL call is an HTTP request with a structured body. Kafka and other queues are covered by message pacts, which test the message content rather than the transport. gRPC and Protobuf need the Pact plugin framework introduced with the V4 specification, installed through pact-plugin-cli.
Why did my Pact JS tests break after upgrading?
Check two releases. Pact JS 16, released in October 2025, made Pact and Matchers point to the V4 DSL and V3 matchers, renaming the old classes to PactV2 and MatchersV2. Pact JS 17, released on 25 June 2026, dropped Node.js 20 and requires Node.js 22 or later. The migration guide in the repository lists earlier breaking changes, such as the CLI moving to @pact-foundation/pact-cli.
Do I need a Pact Broker?
Not to start. You can hand pact files to the provider and verify them from a local path. Once more than one pair of services is involved, or you want can-i-deploy to gate releases, a Broker becomes essential, because it holds the versions, branches, environments and verification results the deployment checks rely on. Self-host the open-source Broker or use PactFlow.
Alternatives
Pact alternatives worth weighing
Resources
Where to go next
Picking the wrong tool costs more than picking late.
QAble advises on toolchain decisions and then implements them, with engineers who have run these tools on real delivery. Start with a free QA audit.