skip to content

Your Pact consumer expectation lists every field the provider returns. Why is that a problem?

level: seniorimportance: should knowfreq 55%

answer

  1. Where does the copied payload come from?
  2. Extra fields in the response are ignored
  3. Each listed field is somebody's constraint
  4. Derive it from the deserialisation target
  5. A loose matcher is still an obligation

basics

~20 s

Every field in the expected body becomes an obligation checked during provider verification, so listing fields the client never reads turns the pact into a schema snapshot that fails on changes which cannot affect the consumer. Fields left out are ignored.

solid answer

~40 s

Pact's body matching is asymmetric on purpose: a field in your expectation but missing from the response fails, while a field in the response but absent from your expectation is ignored. That is what lets many consumers hold different partial expectations of one endpoint. Pasting a full captured response into the expectation throws the benefit away — the provider can no longer remove or retype anything without a red verification build, including fields no consumer reads. The cure is to derive the expectation from the client's deserialisation target and the code paths that read it, keep the fields needed for deserialisation to succeed, and drop the rest rather than loosely matching them. Add a field in the same change that starts reading it.

code

json · 7 lines
json
// Pasted from a live response - 41 fields, 3 of them read
{ "rentalId": 84213, "sku": "cello-half-size", "dueOn": "2026-11-04",
  "warehouseBay": "B7", "lastAuditBy": "svc-audit", "legacyRef": "R-0031",
  "insuranceTier": 2, "...": "..." }

// The expectation this consumer should hold
{ "rentalId": 84213, "sku": "cello-half-size", "dueOn": "2026-11-04" }

go deeper

for a junior

Recall that a Pact expectation should describe what your client needs from the response rather than everything the endpoint returns, and that copying a whole payload into a test is a shortcut with a cost.

for a middle

Explain the asymmetry that makes this work: expected fields must be present, unexpected response fields are ignored. Then show how you derive the field list from the client's deserialisation target instead of a captured response.

for a senior

Show you have paid the cost of a wide contract — red verification builds for changes with no consumer impact, each needing diagnosis and a cross-team conversation — and that you review consumer pacts as changes to another team's freedom to refactor.

for a principal

Own the trade-off at portfolio level: how narrow contracts keep provider teams able to move, why contract credibility collapses after a few false reds, and where the guarantee stops — consumers without pacts are outside it entirely.

## The expectation is the obligation In a Pact consumer test, the expected response body you write is not documentation and not a sample. Every path in it becomes something the provider is checked against during verification: literal paths by equality, matched paths by their matcher. So the question "what should the expected body contain?" is really the question "what do I want to make it impossible for the provider to change?" Pact's body matching is deliberately asymmetric, and the asymmetry is the whole design: - A field **present in the expectation and missing from the response** fails. - A field **present in the response and absent from the expectation** is ignored. Extra data does not fail matching, because the contract records one consumer's needs, not the provider's schema. That second rule is what makes consumer-driven contracts composable: five consumers can hold five different, partly overlapping expectations of the same endpoint, and the provider satisfies all of them by satisfying the union. ## What the two mistakes cost | Field | In the expectation? | Provider changes it | Result | |---|---|---|---| | Client reads it | yes | removed or retyped | verification goes red — correct, this would have broken you | | Client reads it | no | removed | verification stays green, production breaks — a false negative | | Client ignores it | yes | removed or retyped | verification goes red for a change that cannot affect you — a false positive | | Client ignores it | no | anything | provider free to change, nothing to explain | The bottom-right row is the point of the technique. A contract that lists only what is consumed gives the provider a precise, and usually surprisingly large, area of freedom. ## How the wide expectation happens Nobody writes forty fields on purpose. It happens by pasting a real response captured from the running provider into the expectation, because that is the fastest way to get a passing test. In a musical-instrument rental product, a `rental-web` client that needs three fields off `GET /rentals/{id}` — the rental id, the instrument sku and the return date — pastes the whole 41-field payload. Nothing is wrong that day. Two months later the provider team removes two fields nobody consumes, and the 6-minute verification job goes red. Under a regulator's change-record requirement, every red build on a release path has to be investigated and written up, so a change with no consumer impact costs a diagnosis, a cross-team conversation, a consumer-side edit and a record. Do that three times and the contract's credibility is spent: the next genuine red build is assumed to be noise too. ## Deciding what goes in Derive the list from the code, not from the payload: 1. Start from the client's **deserialisation target** and the code paths downstream of it. What does the mapper actually bind, and what does the caller actually read? 2. Keep the fields required for deserialisation to succeed at all — if the mapper fails on a missing non-nullable field, that field is consumed even when no branch reads it. 3. Drop everything else. Not "match it loosely" — drop it. A type matcher on a field you never read is still an obligation to keep sending it. 4. For each surviving field, choose strictness by use: type matching for values you pass through, value or regex matching for values you interpret. 5. Add a field in the **same change** that starts reading it. A pact is a record of current consumption, not a wish list; a field added for next quarter constrains the provider for a quarter in exchange for nothing. ## The judgement, and its limits The discipline is easy to state and awkward in practice, because the pressure runs the wrong way. Adding a field feels safe and costs the consumer team nothing today; the cost lands on another team's release later, as a red build they cannot explain. Reviewing consumer pacts as cross-team-cost changes — a diff to the expectation is a diff to somebody else's freedom to refactor — is what keeps the file honest. It also has a real limit worth saying out loud in an interview. A narrow pact tells the provider which fields at least one consumer needs; it never tells anyone the endpoint is safe to change for a consumer that has no pact at all. The technique's guarantees stop at the set of participating consumers, which is why coverage across consumers matters as much as precision within one.

  • The provider returns a field your client will read next quarter. Do you add it to the pact now?
    No. A pact records current consumption, not intent. Adding it early constrains the provider for a quarter in exchange for nothing, and if the plan changes the obligation outlives the reason for it. Add the field in the same change that starts reading it, which also keeps the contract diff a truthful record of what the consumer needs.
  • How do you work out which fields your consumer actually reads?
    From the code, not the payload. Start at the type the response is deserialised into and follow what the caller touches; keep fields the mapper requires for deserialisation to succeed even when no branch reads them, since those are genuinely consumed. Anything the capture contained but the code never reaches does not belong in the expectation.
  • Does a narrow pact prove the endpoint is safe for the provider to change?
    Only for the consumers that have pacts. A narrow contract tells the provider precisely what participating consumers need, and nothing at all about a client with no pact — an unregistered service, a mobile build, an internal script. Coverage across consumers matters as much as precision within any one contract.

saying these in an interview costs you the question

  • Pastes a captured provider response into the expectation
  • Thinks extra fields in the response fail verification
  • Treats the pact as the provider's full schema
  • Adds fields the consumer might read later
  • Loosens an unread field's matcher instead of deleting it
  • Claims a narrow pact proves the change is safe for everyone