In Mountebank, how does one stub answer the same dive-log request differently each call?
answer
- an array, not a state machine
- one entry consumed per matching request
- the sequence wraps at the end
- an integer field holds one entry longer
- not among the five named behaviors
basics
~20 sMountebank cycles a stub's responses array: each matching request takes the next entry, and the sequence wraps at the end. The repeat field on a response holds that entry for several calls. repeat is a response field, not a behavior.
solid answer
~50 sIn Mountebank, an imposter's stub holds a `responses` array, and the stub serves those entries in turn: the first matching dive-log request gets the first entry, the second gets the second, and once the array is exhausted the sequence wraps back to the start. Each entry is usually an `is` response, carrying the status, headers and body to return literally. An entry may also carry a `repeat` field — an integer greater than zero — which makes that entry answer that many requests before the stub moves on, so `repeat` on a `PENDING` sign-off response holds a polling client for exactly that many calls. In Mountebank, `repeat` is a field on a response and **not** one of the five behaviors, which are `wait`, `copy`, `lookup`, `shellTransform` and `decorate`. WireMock builds the same effect from named states instead of a cycling array.
go deeper
Be ready to say that a Mountebank stub holds a responses array and serves its entries in turn, wrapping at the end. Know that repeat is a field on a response.
Explain that Mountebank's cycling is positional rather than conditional: nothing about the request picks the entry once the stub has matched, and repeat only changes how long one entry is held.
Show where a cycling array is fragile in a real suite. Any extra or retried dive-log call consumes an entry, so the sequence drifts and a test that depends on call ordering becomes sensitive to client behaviour you do not control.
Own the choice of shape for stateful stubbing. Decide when a positional response cycle is enough and when a conversation genuinely needs named states, and what each choice means for who can read and maintain the fixtures.
## How one Mountebank stub answers more than once In Mountebank an **imposter** listens on a port and holds a list of stubs. A stub has `predicates`, which decide whether it matches a request at all, and a `responses` array, which decides what it gives back. The array is the part that carries a conversation. Each matching dive-log request consumes the next entry in order, and once the last entry has been used the sequence wraps round to the first, so a two-entry array alternates for as long as the test keeps calling. Nothing about the request chooses the entry: once the Mountebank stub has matched, position alone decides. Most entries are an `is` response, which carries the status code, headers and body to return literally. A sign-off stub might therefore hold an `is` response reporting `signoffStatus` `PENDING`, followed by one reporting `SIGNED`, and a polling client walks from the first to the second simply by calling twice. ## repeat is a field on a response - In Mountebank, `repeat` lives on an individual entry of a stub's `responses` array, alongside `is`. - In Mountebank, `repeat` takes an integer greater than zero, and the imposter's validation rejects anything that is not one. - In Mountebank, `repeat` holds the stub on that entry for that many matching requests before the cycle advances to the next entry. - In Mountebank, `repeat` is **not** one of the five behaviors, and calling it one is the commonest mistake made about the product. That last point is worth dwelling on, because the two things sit at genuinely different layers. `repeat` decides **which** entry answers; a behavior decides what happens to an entry that has already been chosen. ## The five behaviors sit at a different layer In Mountebank a response may carry a `behaviors` array, and it holds any of exactly five entries, none of which chooses an entry from `responses`. | behavior | product | what it does to a chosen response | |---|---|---| | `wait` | Mountebank | holds the response back before it is returned | | `copy` | Mountebank | copies values out of the request into the response | | `lookup` | Mountebank | pulls values from an external data source into it | | `shellTransform` | Mountebank | pipes the response through an external command | | `decorate` | Mountebank | runs a JavaScript function over the response | Mixing the two layers is why `repeat` is so often miscatalogued as a sixth behavior, and it is a claim an interviewer will notice. ## A dive-log sign-off sequence, step by step 1. A Mountebank imposter is created with one stub whose `predicates` match `GET /dive-log/v1/dives/D-4417/signoff`. 2. Its `responses` array holds two entries: an `is` response with `signoffStatus` `PENDING` carrying `repeat` set to two, then an `is` response with `signoffStatus` `SIGNED`. 3. The client's first poll matches the stub and takes the first entry, which reports `PENDING`. 4. The second poll matches again and takes the first entry once more, because in Mountebank `repeat` has held the cycle there. 5. The third poll advances to the second entry and reports `SIGNED`, and the client's waiting loop exits. 6. A fourth poll wraps the cycle back to the first entry and reports `PENDING` again — the sequence is circular, not terminal. Step six is the one that surprises people. A Mountebank cycle has no natural end state; if a test must keep seeing `SIGNED`, the last entry has to be given a large `repeat` or the client must stop calling. ## Where positional cycling is fragile - Any extra dive-log call consumes an entry, so a retry inside the client silently shifts the whole Mountebank sequence by one. - The mapping from call number to answer is implicit; nothing in the imposter says *why* the second entry is the second, so the fixture is hard to read months later. - Two tests hitting the same Mountebank imposter interleave their consumption of one array, so the sequence depends on execution order. - Because the array is circular, an over-long test loops back to the beginning rather than failing, which hides the drift instead of reporting it. - Counting calls precisely becomes part of the contract, and that is a brittle thing to depend on when the client owns the retry policy. ## Named states are the other shape WireMock expresses the same conversation with named states instead of positions: `inScenario(...)` joins a stub to a machine, `whenScenarioStateIs(...)` limits it to one state, and `willSetStateTo(...)` moves the machine on when the stub serves. The trade is explicitness against brevity — Mountebank's array is shorter to write, while named states say out loud which moment of the exchange each answer belongs to and survive an unexpected retry unchanged.
- In Mountebank, what are the five behaviors, and why is repeat not one of them?Mountebank's behaviors are `wait`, `copy`, `lookup`, `shellTransform` and `decorate`, and they post-process a response that has already been chosen — holding it back, copying request data into it, or transforming it. `repeat` sits at a different layer: it is a field on the response itself, validated as an integer greater than zero, and it decides how long the stub stays on that entry rather than altering what the entry returns.
- What happens in Mountebank if the client retries a dive-log call you did not expect?The retry consumes the next entry of the stub's `responses` array, because in Mountebank the cycle advances on every matching request. A sign-off sequence written as `PENDING` then `SIGNED` hands the retry the `SIGNED` entry early, and every assertion after it slides out of step. That sensitivity to exact call counts is the main cost of positional cycling.
saying these in an interview costs you the question
- Calling repeat one of Mountebank's behaviors
- Thinking the responses array stops at its last entry
- Expecting Mountebank stubs to name states the way WireMock does
- Believing the predicates are re-evaluated to pick the entry
- Assuming every response entry serves exactly one request