A Karate mock feature has to remember data between HTTP requests. Where does that state live, and what puts it there?
answer
- Follow the variables after the response is sent
- One map, owned by the handler
- Background seeds it, scenarios write back
- Response variables are stripped first
basics
~20 sIn a map of global variables owned by the mock handler. The Background seeds it once at start-up, and after every matched Scenario the handler copies that request's variables back, so a def inside a Scenario survives too.
solid answer
~50 sKarate's mock handler keeps a single map of global variables. At start-up it runs the mock feature's `Background` once and copies everything it defined into that map. Each incoming request is then evaluated against those globals - and here is the part people miss - once the matched `Scenario`'s steps have finished, the handler copies that scenario's variables **back** into the globals. So a `* def` inside a `Scenario` survives to the next request exactly as a `Background` variable does; a counter written as `* def id = ~~(id + 1)` genuinely counts up across calls. The response variables are stripped before that write-back, so `response` and `responseStatus` never leak into the next request. The state belongs to the running handler, is shared by every client of that server, and is gone when the server stops.
code
gherkin · 15 linesFeature: counter mock
Background:
* def id = 0
* def m = {}
Scenario: methodIs('post')
* def c = request
* def id = ~~(id + 1)
* c.id = id
* m[id + ''] = c
* def response = c
Scenario: pathMatches('/cats/{id}')
* def response = m[pathParams.id]go deeper
Learn the shape first: the mock keeps its data in ordinary variables, seeded by the Background, and those variables are still there on the next request.
Explain both copies - Background into globals at start-up, and the matched scenario's variables back into globals after each request - and name the response variables as what gets stripped.
Treat the mock's globals as shared state across every test pointed at that port: reason about ordering, plan a reset path, and know that a watch-mode reload wipes it.
Decide whether accumulated mock state is an asset or a liability for your suite, and set the convention: one long-lived mock with an explicit reset contract, or a mock per consumer.
## Where the state actually lives A Karate mock server is one handler object holding one `Map` of global variables plus a runtime per mock feature. That map is the whole of the mock's memory. There is no store on disk, no per-connection session, no journal of past requests - just variables, in the same JVM heap as the test that started the mock. That single map is what makes the leaf's headline fact useful. `Background` running once would be a curiosity if its variables died with the request that first saw them; they do not, because they were never scoped to a request in the first place. ## The two copies that make it work 1. **At start-up.** The handler executes the `Background` once, then copies the variables it produced into the globals map. `* def cats = {}` puts one map object there. 2. **After every matched request.** The handler executes the matched `Scenario`'s steps against a view of those globals, and when the steps finish it copies the resulting variables back into the globals map. Copy 2 is the one candidates forget, and it is the difference between a mock that can only *read* seeded data and one that can *accumulate*. Because of it, three separate patterns all work: - **Mutating a Background object.** `* cats[id] = cat` writes into the map created at start-up. The object is the same one every request sees. - **Reassigning a Background scalar.** `* def id = ~~(id + 1)` replaces a number that the `Background` set to `0`, and the replacement is what the next request reads. - **Introducing a brand-new variable.** A `* def lastSeen = requestPath` in a Scenario creates a global that did not exist before, and later requests can read it. ## What is deliberately excluded The write-back is not indiscriminate. The response variables are removed before the copy, so nothing about the reply the mock just sent survives into the next request. That is why every request starts from a clean `response`, and why a Scenario that sets no status still gets the default rather than inheriting the previous one. | Written in a Scenario | Carried over to the next request? | |---|---| | `* def cats = {}` (or a mutation of it) | yes | | `* def counter = counter + 1` | yes | | `* def anythingElse = ...` | yes | | `* def response = ...` | no - stripped before the copy | | `* def responseStatus = 201` | no - stripped before the copy | ## Consequences you will feel - **Scope is the server, not the client.** Every caller of that port reads and writes the same variables. Two tests pointed at one mock are sharing a database, and should be reasoned about that way. - **Order becomes significant.** A test that creates a record leaves it there for whatever runs next. Passing in isolation and failing in a suite is the classic symptom. - **Nothing resets itself.** There is no per-request, per-scenario or per-test rollback. If a reset is needed, you have to build one - typically an extra `Scenario` that matches something like a `/reset` path and re-initialises the store. - **Concurrency is handled for you.** The handler serialises request processing, so an ordinary read-modify-write on a global needs no locking of your own. ## What ends the state's life Three things, and only three: 1. **Stopping the server.** The handler and its globals are garbage; nothing is persisted. 2. **Starting a second server.** A new `MockServer` builds a new handler with its own globals, and re-runs the `Background`. Two mocks started from the same feature file share nothing. 3. **A hot reload in watch mode.** When watch is enabled the handler checks the feature file's modification time before serving a request and, if it changed, constructs a **replacement handler**. That re-runs the `Background` from scratch and abandons everything the old handler had accumulated. It is a genuinely useful behaviour while authoring a mock and a confusing one if you did not know watch was on - saving the file mid-run silently empties the store. ## Reading the state from the outside The mock's variables are ordinary objects in the same JVM, so a test that started the server can inspect them rather than probing the mock over HTTP. That is worth knowing when the thing you want to assert is *what the mock recorded*, not *what it replied* - a mock's accumulated store is a legitimate observation point, and reaching it through the object beats adding a debug endpoint to the feature file.
- Does a variable first defined inside a mock `Scenario` survive to the next request, or only `Background` variables?It survives. After the matched scenario's steps finish, the handler copies that scenario's variables back into the globals map, so a `def` that appeared for the first time in a Scenario becomes a global like any other. The response variables are the exception - they are stripped before the copy.
- Two tests share one Karate mock and the second one fails on data the first created. What are your options?Either give the mock an explicit reset - a `Scenario` matching something like `/reset` that re-initialises the store, called from setup - or start a separate mock server for the test that needs isolation, since a new server means a new handler, new globals and a fresh `Background` run.
- You start a mock in watch mode and edit the feature file while a run is in progress. What happens to its accumulated data?It is discarded. Watch mode compares the file's modification time before serving a request and, on a change, builds a replacement handler - which re-runs the `Background` and starts from an empty globals map. The old data is not migrated.
Think of one ledger on the server's desk. The Background writes the opening balances, each handled request adds its own entries to that same ledger before the next caller is let in, and closing the shop throws the ledger away.
saying these in an interview costs you the question
- Thinks only Background variables persist across requests
- Expects a scenario def to vanish once the response is sent
- Believes each client or connection gets its own copy
- Assumes the state survives stopping the server
- Thinks the previous response leaks into the next request