What is a provider state in a consumer-driven contract, and how does the provider apply it during verification?
answer
- A precondition, not a request
- Named text agreed between both sides
- Provider registers one handler per name
- Setup, replay, assert, tear down
- Behavioural wording, never storage wording
basics
~20 sA provider state is a named precondition attached to one recorded interaction, such as "a meter with three unbilled readings exists". Before replaying that interaction, the provider runs the setup hook registered under that exact name, then answers the request.
solid answer
~50 sA recorded request such as a lookup for meter 8841 only returns the recorded response if the provider holds matching data, so each interaction carries a named precondition — the provider state. The name is agreed text stored in the contract; on the provider side, a handler is registered per name. During verification the runner looks up the handler for the interaction's state, executes it to seed data or configure a downstream substitute, replays the request, asserts the response, then tears the setup down before the next interaction. States are matched by name, so a rename on the consumer side without a matching handler makes verification fail rather than silently pass. Good states are small and behavioural — one precondition per interaction — rather than one global "the database is loaded" fixture, which recouples every case to a shared dataset.
code
pseudocode · 18 linesproviderStates.register("a meter with 3 unbilled readings exists"):
setup:
store.saveMeter(id = 8841, tariff = "night")
store.saveReading(meterId = 8841, kwh = 14.37, billed = false)
store.saveReading(meterId = 8841, kwh = 9.02, billed = false)
store.saveReading(meterId = 8841, kwh = 11.68, billed = false)
teardown:
store.deleteMeter(id = 8841)
verify(contract):
for interaction in contract.interactions:
handler = providerStates.lookup(interaction.state)
if handler is missing:
fail("no handler for state: " + interaction.state)
handler.setup()
actual = provider.handle(interaction.request)
assertMatches(actual, interaction.response)
handler.teardown()go deeper
Remember that each recorded interaction carries a named precondition, and that the provider must arrange that situation before the recorded request is replayed against it.
Be able to walk the loop — resolve the state, run setup, replay, assert, tear down — and explain why the state name is matched as agreed text rather than inferred from the request.
Show judgement about state design: behavioural naming, parameterisation, teardown discipline, and seeding through the application so verification cannot pass against data the system could never hold.
Own the cost curve. State vocabularies drift into per-record strings and become the maintenance burden that kills adoption; decide what the team standardises and where a shared seeding path belongs.
## Why a precondition is needed at all A recorded interaction says: given this request, the response must look like this. But a request is rarely answerable in a vacuum. "Fetch the unbilled readings for meter 8841" returns an empty list against an empty store, and the consumer recorded a non-empty one because its stand-in was told to return it. Replay against a real implementation therefore needs somebody to arrange the world first. That arrangement, named in the contract and executed by the provider, is the *provider state*. ## The name is part of the contract A state is a string chosen by the consumer while recording — "a meter with 3 unbilled readings exists", "meter 8841 is not registered", "the reading feed is in maintenance mode". It travels with the interaction in the published file. On the other side the provider registers a handler per name, and the verification runner matches them by exact text. That makes the state name a shared vocabulary item with the same weight as a path or a field: renaming it on the consumer side without a corresponding provider handler produces a verification failure, not a silent skip. Many runners also let a state carry parameters (`meterId = 8841`), which keeps the vocabulary small — one parameterised state instead of one string per identifier. ## What a handler does A handler is a small function with setup and, ideally, teardown. Setup does whatever the precondition claims: inserting rows, moving a clock, flipping a feature switch, or programming the substitute that stands in for a dependency below the provider. Teardown removes what setup added, so the next interaction starts from the same baseline. The runner's loop per interaction is: resolve the state, run setup, replay the recorded request through the provider's real entry point, assert the response against the recorded matchers, run teardown. Crucially the handler is production-shaped, not test-shaped: seeding through the same write path the application itself uses keeps the state honest. Handlers that reach around the application to write rows the application could never produce will verify happily against data the system cannot actually hold. ## Designing states well - **One precondition per interaction, phrased behaviourally.** "A meter with 3 unbilled readings exists" describes a business situation. "Row inserted into readings table" leaks the provider's storage into a document the consumer owns, and breaks when storage changes. - **Small, not global.** A single "the database is seeded" state shared by every interaction reintroduces exactly the coupling contract tests were meant to remove: one case's expectations constrain another's data, and a change to the fixture breaks unrelated interactions. - **Deterministic.** Anything the setup derives from ambient conditions — the machine's clock, the runtime locale, a random identifier — makes verification depend on where it ran. - **Few enough to maintain.** A four-person team owning a smart-meter reading feed with two consumers might carry 11 states across 37 interactions. When the state count starts tracking the interaction count, the consumer is usually encoding data rather than situations. ## The failure modes interviewers probe **Unknown state.** The contract names a state the provider has no handler for. A good runner fails loudly; the weak answer is that it "just runs the request anyway", which turns a missing precondition into a mystery assertion failure two lines later. **Leaky state.** Setup without teardown leaves the meter behind, so a later interaction expecting "meter 8841 is not registered" fails — and fails differently depending on interaction order. **Over-specified state.** The state seeds ten fields when the interaction reads three. Now an unrelated schema change breaks a contract that never depended on those fields. **State that cannot exist.** Setup writes a reading with a null timestamp because it inserts directly into storage. The interaction verifies, and the consumer is nonetheless protected against nothing, because production never produces that shape. ## Where states sit relative to the rest of the run The state answers *what must be true*, the recorded request answers *what is asked*, and the matchers answer *what the answer must look like*. Keeping those three separate is what lets a provider refactor its storage and keep every contract green: only the handler bodies change, while the contract file — the agreed vocabulary between the two teams — is untouched.
- How do you keep the number of provider states from exploding as interactions grow?Parameterise them. A state carrying `meterId = 8841` covers every identifier with one vocabulary entry, instead of a new string per record. Beyond that, phrase states as situations rather than data — "a meter with unbilled readings exists" rather than three states differing only in count — and reuse a state across interactions whose precondition is genuinely identical.
- Should a provider state seed data directly into storage or go through the application?Through the application wherever practical. Writing directly into storage can create shapes the application itself could never produce, so verification passes against states production never reaches. Direct writes are a reasonable shortcut for slow or awkward setups, but the risk is that a schema or invariant change silently leaves the seeder producing stale shapes that nothing else in the build exercises.
- What should happen when the provider has no handler for a state named in a contract?The verification of that interaction should fail explicitly, naming the missing state. Continuing without setting anything up converts a missing precondition into a confusing assertion failure on the response, and in the worst case the request happens to succeed against leftover data from a previous interaction, which passes for the wrong reason.
saying these in an interview costs you the question
- Confuses the precondition with the recorded request itself
- Uses one global seeded dataset for every interaction
- Writes state names in storage terms, like table and column
- Omits teardown, so interactions depend on ordering
- Assumes an unknown state name is silently skipped