In WireMock, why can two dive-log endpoints sharing one scenario name break each other?
answer
- the name, not the URL, is the scope
- one current state per name
- a transition anywhere moves it for everyone
- passes alone, fails behind another test
- read which state each name sits in
basics
~20 sA WireMock scenario name identifies one shared state machine. Every stub naming it reads and writes the same state, so two unrelated dive-log conversations advance each other. A stub waiting on a state the other side consumed then never serves.
solid answer
~50 sIn WireMock, `inScenario("dive signoff")` does not scope state to a URL — it joins the stub to a machine identified by that **name**, and every stub using that name shares one current state. Put the dive-upload exchange and the sign-off polling exchange both in `"dive signoff"` and they interleave: a `POST /dive-log/v1/dives` stub firing `willSetStateTo("dive accepted")` moves the machine out from under the sign-off stubs, which are still waiting on `Scenario.STARTED`. The symptom is a test that passes alone and fails in a suite, or fails only when the upload runs first, because the request arrives unmatched while its URL and method are plainly correct. The fix is one scenario name per conversation. WireMock's `getAllScenarios` reports each scenario and the state it currently sits in, which is the fastest way to see that two exchanges are sharing one machine.
code
java · 12 lines// two conversations, two scenario names - each advances on its own
stubFor(post(urlPathEqualTo("/dive-log/v1/dives"))
.inScenario("dive upload")
.whenScenarioStateIs(Scenario.STARTED)
.willSetStateTo("dive accepted")
.willReturn(aResponse().withStatus(202)));
stubFor(get(urlPathEqualTo("/dive-log/v1/dives/D-4417/signoff"))
.inScenario("signoff polling")
.whenScenarioStateIs(Scenario.STARTED)
.willSetStateTo("instructor notified")
.willReturn(aResponse().withStatus(200)));go deeper
Know that a WireMock scenario is identified by the name passed to inScenario, and that every stub sharing that name shares one state. Give each conversation its own name.
Explain the mechanics: a transition on any stub in a WireMock scenario changes the state every other stub in that scenario is compared against, which is why unrelated exchanges must not share a name.
Diagnose the order-dependent failure: a test green alone and red behind another, with a stub that registers fine and never serves. Read WireMock's getAllScenarios for the current state before suspecting the client.
Own the naming convention across suites. Decide who owns scenario names, how they stay unique when several teams stub the same dive-log API, and whether stateful fixtures should be shared at all given the coupling they introduce.
## What inScenario actually scopes In WireMock, `inScenario("dive signoff")` takes a **name**, and that name is the entire identity of the state machine. It is not scoped to a URL, a method, a stub, a test class or a thread. Every stub anywhere in the server that passes the same string to `inScenario(...)` reads and writes the same current state, and any of them declaring `willSetStateTo(...)` can move it for all the others. That sharing is the feature. It is what lets a WireMock stub for `POST /dive-log/v1/dives` set up the state that a later stub for `GET /dive-log/v1/dives/D-4417/signoff` requires, so a conversation can span endpoints. It is also the trap, because nothing warns you when two exchanges that have nothing to do with each other pick the same name. ## How two conversations collide 1. A dive-upload fixture registers a WireMock stub for `POST /dive-log/v1/dives`, with `inScenario("dive signoff")`, requiring `Scenario.STARTED` and declaring `willSetStateTo("dive accepted")`. 2. A sign-off fixture, written by someone else, registers WireMock stubs for `GET /dive-log/v1/dives/D-4417/signoff` in the same `"dive signoff"` scenario, the first of them requiring `Scenario.STARTED`. 3. Run the sign-off test alone and the machine is in `Started`, so its first stub passes the gate and the test is green. 4. Run the upload test first and it serves, moving the shared machine to `"dive accepted"`. 5. The sign-off test now meets a machine sitting in `"dive accepted"`. Its first stub requires `Started` and is not a candidate, and no sign-off stub requires `"dive accepted"` either. 6. The sign-off request goes unmatched, and the failure reads as a missing payload rather than as a scenario mismatch. ## Why it presents as flake - It is order-dependent: green alone, red behind another test, which is the classic profile teams label flaky and retry. - It is invisible in the stub definition, because in WireMock the URL, method and body matchers are all correct and re-reading them tells you nothing. - It survives re-running the single test, so the usual first move confirms the wrong hypothesis. - It gets worse as suites grow, since a name chosen for one conversation is easy for a third fixture to reuse. - It cannot be fixed by tightening the WireMock URL matcher, because the URL was never what rejected the request. ## Reading where the machine is WireMock's `getAllScenarios` reports each scenario and the state it currently sits in, and that single reading usually ends the investigation: if two exchanges you believed were independent show up under one scenario name, you have found it. For setting up rather than diagnosing, WireMock's admin surface exposes `PUT /__admin/scenarios/{name}/state`, which names the scenario in the path and moves it to the state you ask for. That lets a test start part-way through a long conversation instead of replaying every earlier call to get there, which is worth having as soon as a conversation is more than two states deep. ## Rules that keep conversations apart - One WireMock scenario name per conversation, chosen to describe the exchange rather than the endpoint or the test. - Keep the names in one shared constant, so a second fixture cannot reuse one by accident and a typo becomes a compile error. - Prefer several short conversations to one long machine that many unrelated stubs touch. - If a stub needs no state at all, leave it out of every WireMock scenario rather than parking it in a convenient one. - Make every state a `willSetStateTo(...)` can reach also a state some stub requires, so the machine never lands somewhere nothing answers. - Treat a scenario name as part of the fixture's public surface, because renaming one on a single stub silently splits one conversation into two machines that no longer see each other's transitions. ## The shape of the same risk elsewhere In MockServer the equivalent sequencing is carried by `Times.exactly(`, `Times.once(` and `TimeToLive` on individual expectations, which cap usage and lifetime per expectation, so the coupling shows up as expectations quietly using up their allowance rather than as a shared named machine. Either way the mechanism to keep straight is the same one: whatever carries a conversation forward is named, it is server-side, and two fixtures that name the same thing are one fixture.
- In WireMock, how do you begin a test part-way through a long conversation?Drive the machine directly rather than replaying the earlier calls. WireMock's admin surface exposes `PUT /__admin/scenarios/{name}/state`, which names the scenario in the path and moves it to the state you ask for, so a test can jump straight to `instructor notified` and assert only the last leg of the dive-log exchange.
- In WireMock, what does a stub that joins a scenario but requires no state do?It serves in every state of that scenario. In WireMock, `inScenario(...)` without `whenScenarioStateIs(...)` adds no state condition, so the stub answers throughout the conversation. That is occasionally useful for a background dive-log endpoint that must always reply, but it also means it keeps serving after the exchange has moved on.
saying these in an interview costs you the question
- Assuming scenario state is scoped per URL or per stub
- Reusing one scenario name for every stateful stub in a suite
- Thinking two stubs cannot transition the same machine
- Blaming order-dependent failures on the client under test
- Expecting a scenario to return to its start state on its own