What does a Pact consumer test do at runtime, and when does it write the pact file?
answer
- Something real is listening on a port
- Your production client makes the call
- Expectations are checked when the test ends
- A failing run leaves no artefact behind
- JSON appears under the build directory
basics
~20 sA Pact consumer test starts a local mock HTTP server, registers the expected interactions on it, and runs the real client code against that server. The pact JSON file is written only when every registered interaction was requested and matched.
solid answer
~40 sThere are three moving parts. First you declare **interactions** — request criteria plus the response to return — against a **mock provider**, a real HTTP server Pact starts on a local port. Second, the test body must drive your **production client** at that mock's URL; a test that hand-builds the request, or mocks the HTTP transport, records a call your code never makes and proves nothing. Third, when the test body ends Pact checks that every declared interaction was actually requested and matched, and fails the test otherwise. Only then does it serialise the pact file — `target/pacts` under Maven or `build/pacts` under Gradle in Pact-JVM, or the `dir` you configure in pact-js. Nothing in this run touches the real provider.
code
java · 27 lines@ExtendWith(PactConsumerTestExt.class)
@PactTestFor(providerName = "instrument-catalog")
class RentalCatalogClientTest {
@Pact(consumer = "rental-web")
RequestResponsePact instrumentById(PactDslWithProvider builder) {
return builder
.given("instrument 4718 exists")
.uponReceiving("a request for instrument 4718")
.path("/instruments/4718")
.method("GET")
.willRespondWith()
.status(200)
.body(new PactDslJsonBody()
.stringType("id", "4718")
.stringType("model", "upright-piano"))
.toPact();
}
@Test
void readsTheInstrument(MockServer mockServer) {
// the production client, pointed at the mock provider's URL
Instrument found = new RentalCatalogClient(mockServer.getUrl())
.findById("4718");
assertEquals("upright-piano", found.model());
}
}go deeper
Recall that a Pact consumer test runs against a local mock HTTP server rather than the real service, and that its output is a JSON file describing the calls your client made and the responses it expects back.
Be ready to walk the mechanics end to end: declaring interactions, Pact starting the mock on an ephemeral port, injecting that URL into the production client, and the end-of-test check that gates whether the pact file is written at all.
Show that you review consumer tests for the over-mocked variant, where a hand-built request or a mocked transport produces a contract nobody's code will honour. Explain why a failed run must never leave a publishable artefact on disk.
Own the boundary this test sits on: what belongs in fast consumer tests versus a real integration environment, and how the team keeps the mock dumb — one interaction per case, no branching logic — so the suite stays unit-test cheap as consumers multiply.
## What a Pact consumer test actually starts A Pact consumer test is an ordinary unit test with a real HTTP server attached to it. When the framework integration runs — `PactConsumerTestExt` in Pact-JVM, or `PactV3.executeTest` in pact-js — Pact starts a **mock provider**: an HTTP server bound to a local port and pre-loaded with the interactions this test declared. It is not a mocking-library test double sitting in your object graph. Your client opens a socket to it, sends bytes, and reads a real response, which is precisely why the exercise proves something about the client's own serialisation, headers and URL building. An **interaction** is the unit the mock serves: a description, an optional provider state name, request criteria (method, path, query, headers, body) and the response the mock will return when a request satisfies those criteria. ## The four phases of one test 1. **Declare.** The DSL registers one or more interactions with the mock provider. Nothing has run yet; you are describing a conversation you expect your client to have. 2. **Start.** Pact boots the mock server and hands the test its address — a `MockServer` parameter with `getUrl()` in Pact-JVM, the mock server object passed into `executeTest` in pact-js. The port is normally ephemeral, so the base URL must be injected into the client, never hard-coded. 3. **Exercise.** The test constructs the **production client** with that base URL and calls the method under test, then asserts on the object the client returned. 4. **Verify and write.** When the test body finishes, Pact checks that every declared interaction was actually requested and that each request matched. Only if that check passes does it serialise the interactions to a pact file. ## Why the production client must make the call The single most common way to make a consumer test worthless is to build the request by hand inside the test — a bare HTTP call, or a client whose transport has itself been mocked. The pact then records a request your production code never sends. Path construction, query encoding, `Content-Type` and `Accept` headers, date formats and JSON serialisation all belong to the client; if the client is bypassed, none of them are under contract. The rule is: the only thing standing in for reality is the mock provider, and everything on your side of the wire is the real code. For the same reason the assertion at the end of the test should be on the **deserialised result** — the domain object your client produced — because that is what proves the response shape you recorded is actually consumable by the code that will read it in production. ## When the pact file is written, and when it is not | Situation | What the mock does | Test outcome | Pact file | |---|---|---|---| | Every declared interaction requested and matched | returns the recorded response | pass | written | | Request differs on path, method, header or body | returns an error response and records the mismatch | fail | not written | | A declared interaction is never requested | nothing | fail — missing interaction | not written | | Client sends a request no interaction covers | returns an error response and records it as unexpected | fail | not written | That last-column behaviour matters more than it looks: a pact file on disk is evidence that a passing test drove those exact calls. A failed run must not leave a stale contract behind for a build step to publish. In Pact-JVM the file lands in `target/pacts` under Maven and `build/pacts` under Gradle, overridable with the `pact.rootDir` system property; in pact-js the `dir` option on the `PactV3` constructor chooses the directory. One file holds one consumer/provider pair and is named for both, so several test classes writing interactions for the same provider accumulate into the same file. ## What the test does not prove Nothing in this run touches the provider. The mock provider returns exactly what you told it to return, so a green consumer test says only "my client can consume a response of this shape, and here is the shape". Whether the real provider produces it is decided later by a separate provider-side verification run reading this file. Treating a green consumer suite as evidence the integration works is the misunderstanding that makes teams distrust the tool. The other half of that point is scope. The mock is deliberately dumb: it does no routing logic, no persistence, no business rules. If your test needs the mock to behave differently for a second call, that is a second interaction, not conditional logic — which is also why consumer tests stay fast enough to sit beside unit tests rather than in an integration suite.
- Why does it matter that the test drives the production client rather than making the HTTP call itself?Because the pact records the request that was actually sent. Path building, query encoding, `Content-Type` and `Accept` headers, and JSON serialisation all belong to the client. Build the request by hand and the contract describes a call your code never makes, so the provider can satisfy the pact and still reject your real traffic.
- What does the Pact mock provider do when the client sends a request no declared interaction covers?It does not guess a close match. It returns an error response rather than any recorded body, and records the call as an unexpected request, so the end-of-test check fails and no pact file is written. The same applies in reverse: a declared interaction that is never requested fails the test as a missing interaction.
- A colleague says the consumer suite being green proves the integration works. What is wrong with that?The mock returns exactly what the test told it to return, so a green consumer suite only proves the client can consume that shape and records the shape it needs. Whether the real provider produces it is decided by a separate provider-side verification run that replays this file against the running service.
The mock provider is a rehearsal partner reading agreed lines: your client plays its real part, and the transcript is kept only if the whole scene ran as written.
saying these in an interview costs you the question
- Thinks the pact file is written even when the test fails
- Mocks the HTTP client instead of calling the mock provider
- Believes the consumer test contacts the real provider service
- Says a green consumer test proves the integration works
- Hard-codes the mock server's port instead of injecting its URL
- Treats an interaction that was never requested as harmless