Writing PACT Tests
Theory understood. Now write real contracts. This lesson covers the full PACT consumer–provider cycle in JavaScript: matchers that make contracts flexible, provider states that set up test data, and the PACT Broker that stores contracts between pipeline runs.
1 The Hook
A Wellington microservices team adopts PACT after reading about it. The developer writes a consumer test that hardcodes the exact response: { "id": 123, "name": "Aroha Williams", "created": "2026-06-08T09:00:00Z" }. The provider verifies it — test passes. Three months later the provider changes the timestamp field name to created_at (snake_case) as part of an internal tidyup.
The consumer test breaks. Investigation reveals the consumer test was asserting the exact field name AND the exact timestamp value, not just the shape of the contract. The timestamp "2026-06-08T09:00:00Z" is hardcoded — it’ll fail the moment the provider’s test database uses any other date.
PACT matchers exist precisely to avoid this. The team had written contracts, but hadn’t used them correctly. Contracts asserting exact values collapse on any data change. Contracts using matchers survive provider evolution gracefully.
2 The Rule
PACT matchers define the TYPE and FORMAT of a field, not its exact value. Contracts that assert exact values will break on any data change, defeating the purpose of consumer-driven contracts.
3 The Analogy
A good contract versus a bad contract.
A good supply contract says “deliver 10 boxes of apples, each weighing at least 100g.” A bad supply contract says “deliver box number 4821 containing apple serial number XK-392.”
The first survives a supplier changing their inventory system, their warehouse, or their apple variety. The second breaks every time the supplier updates their internal tracking. In PACT terms: string('Aroha Williams') is the first contract. The literal "Aroha Williams" is the second. Use matchers — they survive provider internals changing.
4 Watch Me Do It
A full PACT consumer–provider cycle for a NZ payments platform. The PaymentsService (consumer) calls the CustomersService (provider) to fetch customer details before processing a payment.
Consumer test with proper matchers:
Provider state handler — provider side, sets up the data before PACT sends the request:
Don’t forget error paths — test the 404 contract too:
'Aroha Williams') are used by the PACT mock server to generate the response your consumer code sees during the consumer test. The provider verification only checks that the field is the right type and format — not that exact value. Matchers are about shape, not data.5 When to Use It
Write consumer tests for every interaction your service has with another service — happy path and error paths. A contract missing the 404 and 401 cases gives you false confidence: your consumer code handles errors, and that error-handling logic has a contract too.
Provider states are needed whenever the response depends on data — which is almost always. A request for a customer who exists should return 200. A request for a customer who doesn’t exist should return 404. Those are two different provider states, and you need a handler for each.
Write the consumer tests first. The consumer owns the contract. The provider team reads the contract and writes the state handlers to match. Never reverse this flow.
6 Common Mistakes
🚫 “I used to think: I should use exact values in PACT matchers to make the contract strict.”
Actually: Exact values make contracts brittle. They fail when test data changes, not when the API contract changes. Use string(), number(), regex() to specify shape, not value. Reserve exact literals only for fields where the value itself is part of the contract — like a fixed enum: currency: 'NZD'.
🚫 “I used to think: I only need to write consumer tests for the happy path.”
Actually: Error contracts matter just as much. If your consumer expects a 404 to have { error: '...', code: '...' }, that is a contract. Test it — otherwise a 404 with a different shape silently breaks your error handling in production. Every interaction type that your consumer code branches on needs its own contract.
🚫 “I used to think: provider state handlers can share the same database as my unit tests.”
Actually: Provider state handlers need isolated, predictable data. Use a separate test database or run migrations before each verification. The state handler name is a promise: “customer cust-001 exists and is active” must be true when PACT sends the request. Shared databases with other tests running concurrently will break that promise unpredictably.
7 Now You Try
A NZ KiwiSaver portal’s notification-service calls the member-service to get contact preferences before sending emails. Write a PACT consumer test for GET /members/{id}/contact-preferences. The response should include: email (string), mobile (optional string), preferredChannel (string, one of 'email', 'sms', 'both'), and emailVerified (boolean). Include a provider state for 'member m-001 exists with email preferences' and a 404 contract for 'member m-999 does not exist'.
8 Self-Check
Click each question to reveal the answer.
Q1: Why should you use string() instead of a literal string value in a PACT matcher?
string('Aroha Williams') tells PACT: the field must be a string type — the value 'Aroha Williams' is just used as example data in the mock response. A literal "Aroha Williams" asserts that exact value, so the provider verification fails the moment the test database has a different name. Matchers make contracts resilient to data changes while still catching structural changes (like a field being renamed or removed).
Q2: What is a provider state and what does its handler do?
A provider state (e.g. .given('customer cust-001 exists and is active')) is a named precondition the consumer declares. The provider’s state handler is the code that makes that precondition true — seeding a database row, creating a mock object, or deleting a record. When PACT runs provider verification, it calls the state handler first, then sends the consumer’s request, then checks the response matches the contract. Without state handlers, the provider has no way to guarantee the data conditions the consumer’s test requires.
Q3: Should you test error-path interactions (404, 401) in PACT? Why?
Yes. Your consumer code branches on error responses — a 404 triggers one code path, a 401 triggers another. If your consumer expects { error: '...', code: '...' } from a 404, that response shape is part of the contract. If the provider changes it to { message: '...' }, your error handling silently breaks. Write a separate PACT interaction for each response status your consumer handles, not just the happy path.
9 ISTQB Mapping
Consumer-driven contract testing is a specific form of interface testing where the consumer generates the interface specification and the provider verifies it. PACT matchers correspond to type-based interface validation rather than value-based assertion — matching the CTAL-TA distinction between structural and behavioural interface testing.
Section 5.2 — Test doubles: provider state handlers are a form of test fixture management analogous to test doubles. The state handler sets up the provider’s world so the interaction can be verified in isolation — the same pattern as a stub setting return values before a unit test runs.
10 Next Steps
You can write correct PACT contracts. The next question is: how do you stop a provider deploying when it breaks one? That requires the PACT Broker and can-i-deploy.