skip to content

In a Karate mock feature file, how does a matched Scenario produce the HTTP reply — what do you write instead of returning a response object?

level: juniorimportance: must knowfreq 72%

answer

  1. a request handler, not a function
  2. you assign, you never return
  3. the handler reads variables after the last step
  4. the status code has a convenient default

basics

~10 s

A mock scenario returns nothing. It assigns ordinary variables — response, responseStatus, responseHeaders, responseDelay — and after the last step Karate reads them and assembles the reply, so declaration order does not matter.

solid answer

~50 s

A Karate mock `Scenario` is a request handler, not a function that returns. You write `* def response = { ... }` and, where you need them, `* def responseStatus = 404`, `* def responseHeaders = { ... }` and `* def responseDelay = 200`. Once the last step has run, the mock handler reads those variables out of the scenario's scope and builds the HTTP reply from whatever they hold. `responseStatus` defaults to **200**, so happy paths set only `response`. The body is serialised from its runtime type — a Map or List becomes JSON, a string text, an XML node XML — so there is no template language involved. Because assembly happens after the steps, order is irrelevant and a later step may overwrite an earlier value; a step that fails throws all of it away and answers 500 with the error message.

code

gherkin · 14 lines
gherkin
Feature: cat store mock

Background:
  * def cats = { '1': { id: '1', name: 'Billie' } }

Scenario: pathMatches('/cats/{id}') && methodIs('get')
  * def cat = cats[pathParams.id]
  * def response = cat
  * def responseStatus = cat ? 200 : 404

Scenario: pathMatches('/cats') && methodIs('post')
  * def responseStatus = 201
  * def responseHeaders = { 'Location': '/cats/2' }
  * def response = { id: '2', name: 'Wild' }

go deeper

for a junior

Remember the four names and that you assign them rather than return anything. Setting only the response variable gives you a 200 with a JSON body, which covers most stubs you will write in your first week.

for a middle

Be able to explain that assembly happens after the last step: that is what makes assignment order irrelevant, lets a later step overwrite an earlier one, and makes the body's runtime type decide the serialisation.

for a senior

Know the failure path. A failed step answers 500 with a Karate error string and skips your configured headers, CORS and delay — so a suite that suddenly sees fast 500s is usually reporting a broken mock, not a broken endpoint.

for a principal

The tradeoff is that a mock built this way is real code with real failure modes. Decide how much logic a stand-in may carry before it needs its own tests, and where a team should stop and run the real service instead.

## A mock scenario is a handler, not a function In a Karate mock feature file every `Scenario` is a request handler. Its name is the JavaScript expression that decides whether it takes the request; its body is a list of ordinary steps. Nothing in that body is a `return`. When the last step finishes, the mock handler reads a fixed set of variables out of the scenario's own scope and builds the HTTP reply itself. That one design choice is why a Karate mock needs no template language. The body is computed by the same `* def`, `* eval`, `call` and `read()` steps you already use in a test, so a stand-in can grow real behaviour — look up a stored record, branch on a query parameter, echo part of the request — without leaving Gherkin. ## The variables the handler reads | variable | type | default | what it becomes | |---|---|---|---| | `response` | any Karate data type | none | the response body | | `responseStatus` | number | `200` | the status code | | `responseHeaders` | map | none | headers merged onto the reply | | `responseDelay` | number, milliseconds | none | how long the server waits before writing | - `responseStatus` defaulting to **200** is why happy-path scenarios set only `response`. - Leaving `response` unset is legal: the caller gets a `200` with an empty body and no inferred `Content-Type`, which is a common cause of a client that "hangs" on an empty parse. - `responseHeaders` is **merged key by key** onto whatever the server-wide defaults already put there; it does not replace the whole header set. ## The body is typed, not templated Karate serialises `response` from its runtime type, so you never write a serialiser: - a **Map or List** goes out as JSON; - an **XML node** goes out as XML; - a **string** goes out as text; - a **byte array** goes out untouched, so binary fixtures need no encoding step. Dynamic values come from whole-value embedded expressions — `{ id: '#(nextId())' }` — or from `read('cats-response.json')`, which resolves embedded expressions in the file it loaded. ## Order does not matter, and the last write wins Because assembly happens *after* the last step rather than at a `return`, the sequence of the assignments is irrelevant. These two scenarios are identical: ```gherkin Scenario: pathMatches('/cats/{id}') * def responseStatus = 404 * def cat = cats[pathParams.id] * def response = cat * if (cat) karate.set('responseStatus', 200) ``` versus setting the status last. A later step may overwrite an earlier value freely, which is what makes multi-step request processing — validate, look up, decide, shape — natural to express. ## Computing the body from the request Because the reply is just data in a variable, the request is available as ordinary data too while you build it. A scenario can read the path segments its matcher captured, a query parameter, or the parsed request body, and assemble the reply out of them: ```gherkin Scenario: pathMatches('/orders/{id}') && methodIs('put') * def response = { id: '#(pathParams.id)', total: '#(request.total)', state: 'UPDATED' } * def responseStatus = 200 ``` This is the capability that separates a Karate mock from a fixed canned reply: nothing here is a placeholder syntax that a stub engine has to interpret, it is the same expression language the rest of the feature file uses. The cost is that a mock can grow real logic, and real logic can fail — which is exactly what the next section is about. ## When a step fails, none of it survives A step that fails inside a matched mock scenario does **not** produce the reply the scenario had already built. The handler abandons it and answers **500** with the failure message as the body. Three things go with it, because they are applied only on the success path: 1. the header map from the server-wide `configure responseHeaders`; 2. the CORS `Access-Control-Allow-Origin` header, when CORS is switched on; 3. `responseDelay` — the 500 comes back immediately. So a consumer test that starts failing with a fast 500 carrying a Karate error string is telling you the mock's own scenario broke, not that the endpoint under test returned 500. ## Why the reply variables do not leak into the next request A mock's `Background` variables deliberately outlive a request, and each request's new variables are folded back into that long-lived state. The reply variables are the deliberate exception: the handler strips them out before it takes that snapshot. Without that, one scenario's `responseStatus` of `404` would become the starting value for every later request, and the `200` default would be a lie after the first miss. Knowing the exception exists is what lets you keep a counter or a store in `Background` and still trust that each reply starts from a clean slate.

  • What does a Karate mock send when the matched scenario never sets `response` at all?
    An empty body. `responseStatus` still defaults to 200, so the caller gets a 200 with zero bytes and no inferred `Content-Type` — which a strict JSON client will usually surface as a parse error rather than as a missing body. Set `response` explicitly, even to `{}`, whenever the contract promises a payload.
  • A mock scenario builds the body and then a later step in the same scenario fails. What does the caller receive?
    A 500 whose body carries the failing step's error message. The `response` the scenario had already built is discarded, and the server-wide `configure responseHeaders` map, the CORS header and `responseDelay` are all skipped, because the handler applies those only when every step passed.
  • Can two different steps in one mock scenario both assign `response`?
    Yes, and the last assignment wins. Nothing is read until the scenario is finished, so a scenario can set a default reply first, then overwrite it inside an `if` branch. That is the idiomatic way to express “happy path unless” without nesting.

It is a paper form on a counter rather than a sealed envelope you hand over. You fill in the body box, the status box and the header box in any order you like, and the clerk collects the whole form once you put the pen down and posts it for you.

saying these in an interview costs you the question

  • Says the value of the last step is returned as the response body.
  • Believes a mock needs a template language to build a dynamic body.
  • Thinks responseStatus must always be set, not knowing 200 is the default.
  • Assumes a failed step still serves the response the scenario had built.
  • Claims the order of the assignments decides which variable wins.