Chapter 06 of 10
Stateful Mocks: When the Attempt Needs Memory
Why a create followed by a list is the flow every stateless mock fails, what state buys you in a cart or a wizard, and where memory turns a mock into a bad backend.
Most mocks are stateless, and for most endpoints that is correct. It stops being correct the moment the thing you are building is a sequence, because a sequence is defined by the second request knowing about the first.
The flow every stateless mock fails
Create something, then list. The list is unchanged, because the list rule has one template and the create rule never touched it.
That single behaviour is enough to make an entire class of feature unbuildable against a mock. That covers a cart that never fills, a wizard whose second step does not know what the first one submitted, a status that stays pending forever and an upload that never appears in its collection. The developer works around it by hard-coding the next screen, and the workaround is thrown away on integration day along with whatever it was hiding.
What state buys
Three situations justify the extra machinery, and they have in common that the interesting behaviour lives between the calls rather than inside one.
A collection that grows. Adding an item and seeing it in the next read is the minimum a create-and-list screen needs to be built honestly, including the ordering and the empty state on either side of it.
A value that advances. An order that moves from pending to confirmed on the next poll lets you build the polling, the intermediate rendering and the terminal state. A mock that returns confirmed immediately lets you build none of that.
A key that has been seen before. Idempotency only exists as behaviour across two requests. A mock that records the keys it has answered can return the original response to a repeat, which is the only way a client's retry logic gets exercised before production.
Where memory makes things worse
State is a slope, and the far end of it is a second implementation of your backend with none of the tests.
The first condition is easy to justify and the fifth is not. Once the mock is validating that the quantity is available, recalculating a total, rejecting a transition from one status to another and expiring a reservation after a timeout, the mock has business logic. Business logic in a mock has two problems. It is a guess about what the real service will do, and it will be maintained by whoever the guess inconveniences next.
There is also a practical limit. A mock's state is usually shared by everyone pointing at that mock, so two developers working on the same flow interfere with each other. A test suite running in parallel sees whatever the other run left behind. That is tolerable while the state is a list of three items and intolerable once somebody is asserting on it.
The line worth holding is that a mock may remember and should not decide. Recording what happened is what makes a sequence buildable, while judging whether it was allowed is the provider's job and the thing your contract tests will check against the real service.
Keep it resettable
Any mock with memory needs an obvious way back to the starting position, because the value of a reproducible flow disappears the moment the state has drifted.
In practice that means a defined initial state that a reset returns to, and a habit of resetting before a demo or a test run rather than after something has gone wrong. It also means being deliberate about how much is remembered, since a mock holding a few records is easy to reason about and one holding a session's worth of history is not.
In Routebase
A rule can be given State Management, which turns it from a template into something that remembers.
The state starts from an Initial State written as JSON, such as a counter at zero and an empty array. Response bodies read it with {{state.variableName}} placeholders alongside the usual faker and request values, so a list rule can return whatever has accumulated rather than a fixed set.
Mutation happens through a __state__ block in the body template, which is how a POST rule records what it was sent and a subsequent GET rule reflects it. Because state sits on the rule rather than on the server, a flow can be stateful exactly where it needs to be while every other endpoint stays a stateless template that nobody has to reset. The Mock Server guide covers initial state, reads and mutation.
Frequently asked questions
What is a stateful mock?
A stateful mock remembers something between requests, so a POST changes what the following GET returns. Without that, a mock answers every call from the same fixed template and a create followed by a list shows no sign that anything was created. State is what makes a multi-step flow buildable before the backend exists.
When does a mock need state?
Whenever the feature you are building is a sequence rather than a screen. A cart, a checkout, a multi-step form, a status that moves from pending to confirmed and anything involving an idempotency key all depend on the second request knowing about the first. A read-only list or a detail view needs nothing of the sort.
What are the limits of a stateful mock?
State turns a mock into a small implementation, and an implementation has to be maintained and reasoned about. Once the rules are enforcing invariants, rejecting conflicts and calculating derived values, you are writing the backend twice and the copy has no tests. That is the point where the flow belongs against the real service.
How do you mock an idempotent endpoint?
Keep the keys you have already seen and branch on whether the incoming one is among them. A first request records the key and returns the created resource. A repeat of the same key returns that same response without creating anything again, which is exactly what the client's retry logic has to be built against.
Last reviewed by The Routebase Team.