skip to content

In a Karate mock feature, what do configure responseHeaders and configure cors = true in the Background do to every reply, and how does a scenario's own responseHeaders interact with them?

level: middleimportance: must knowfreq 54%

answer

  1. server-wide settings, not per-request ones
  2. the Background is the only start-up code
  3. the scenario map lands on top, key by key
  4. preflight is answered before matching runs
  5. the failure paths get none of it

basics

~20 s

Both are server-wide and belong in the Background. configure responseHeaders gives every successfully served reply a header map; configure cors = true answers OPTIONS preflights and adds Access-Control-Allow-Origin. A scenario's own responseHeaders merges over that map, key by key.

solid answer

~40 s

Both are read from the `Background`, which runs once when the mock server starts, so they configure the server rather than a request. `configure responseHeaders = { 'Content-Type': 'application/json' }` gives every successfully served reply that header map. `configure cors = true` makes the handler answer an `OPTIONS` preflight itself — 200 plus the allow-origin, allow-methods and allow-headers headers, **before** any scenario matching — and adds `Access-Control-Allow-Origin: *` to every reply. A scenario's own `* def responseHeaders = { ... }` is then merged **over** the configured map key by key, so overriding `Content-Type` for one endpoint keeps the other configured headers. The catch is that all of this is on the success path only: an unmatched 404 and a failed-step 500 carry neither the configured headers nor the CORS header.

code

gherkin · 13 lines
gherkin
Feature: catalogue mock

Background:
  * configure cors = true
  * configure responseHeaders = { 'X-Env': 'mock', 'Cache-Control': 'max-age=60' }

Scenario: pathMatches('/items')
  * def response = [{ id: 1 }]

Scenario: pathMatches('/items/live')
  # overrides only Cache-Control, X-Env still arrives
  * def responseHeaders = { 'Cache-Control': 'no-store' }
  * def response = [{ id: 1, live: true }]

go deeper

for a junior

Recall that both settings go in the Background and apply to the whole mock server, and that a scenario's own responseHeaders is the per-endpoint override rather than a replacement.

for a middle

Explain the merge order: the configured map is applied first, then the scenario map over it key by key, so an override keeps the other configured headers. Know that CORS also short-circuits OPTIONS before matching, and that Content-Type usually comes from the body's type rather than from either map.

for a senior

The debugging value is knowing what the failure paths do not get. A 404 or a step-failure 500 carries no configured headers and no CORS header, which is why a browser reports a matcher bug as a CORS error.

for a principal

Decide how much a shared mock may diverge from the real service's header contract. A convenient default map hides header bugs that only production will find, so weigh test convenience against fidelity when a mock is many teams' stand-in.

## Two knobs that belong to the server, not to a request A mock feature's `Background` is the only part of the file that runs at server start-up, and it is where both of these settings are read. The mock handler harvests them once, when it initialises the feature, and keeps them for the life of the server: ```gherkin Background: * configure responseHeaders = { 'X-Env': 'mock', 'Cache-Control': 'max-age=60' } * configure cors = true ``` Putting them anywhere else is a mistake of kind, not of style. They describe how *the mock server* answers, so there is no request context in which they would mean anything; the per-endpoint knob is the `responseHeaders` **variable**, which is a different thing with a deliberately similar name. ## `configure responseHeaders`: a default header map The map is applied to the reply of every request that a scenario matched and served successfully. It exists because most mocks carry the same handful of headers everywhere — a caching directive, an environment marker, a trace id — and repeating them in twenty scenarios is how a mock drifts. Note that the media type is usually **not** what you need it for: Karate already derives `Content-Type` from the runtime type of `response`, so a Map body is announced as JSON without any configuration. ## The scenario-level variable merges, it does not replace When a scenario sets `* def responseHeaders = { ... }`, the handler applies the configured map first and then the scenario's map **over it, key by key**. That has two consequences worth stating out loud: - a scenario that overrides one header **keeps** every other configured header — this is pinned by Karate's own test, where a configured `X-Special-Header` and a scenario's `X-Custom-Header` both arrive on the same reply; - there is no way to *remove* a configured header from one scenario, only to give it a different value. If one endpoint must not carry a header, it does not belong in the configured map. ## `configure cors = true`: preflight plus a wildcard origin Switching CORS on does two separate things: 1. an `OPTIONS` request is answered directly by the handler with `200`, an `Allow` header, an `Access-Control-Allow-Origin: *`, the allowed-methods header, and the requested headers echoed back — **before any scenario matching happens**; 2. every reply a scenario serves successfully also carries `Access-Control-Allow-Origin: *`. Point 1 is the one that surprises people: while CORS is on you cannot write your own `OPTIONS` scenario, because the preflight short-circuit runs first and your scenario is never consulted. ## The replies these settings never reach Both settings live on the **success path only**, which is the highest-value detail in this whole area: | outcome | configured headers | CORS header | delay | |---|---|---|---| | a scenario matched and every step passed | applied | applied | applied | | a step in the matched scenario failed (500) | skipped | skipped | skipped | | no scenario matched at all (404) | skipped | skipped | skipped | The 404 row is the one that costs debugging hours. A browser calling a CORS-enabled Karate mock on a path no scenario matches gets a 404 **with no `Access-Control-Allow-Origin` header**, so the browser reports it as a CORS failure rather than as a 404. The console says "blocked by CORS policy"; the actual defect is a matcher that does not match. Check the mock's log for the *"no scenarios matched, returning 404"* line before you touch the CORS configuration. ## A disclosure header you did not configure Karate 2.x adds one header of its own on top of whatever you configured: every reply the mock **fabricates** carries `Karate-Mock: true`. It is on by default, it is stamped even on the unmatched-request 404, and it is deliberately *not* stamped on a reply that came from a real upstream through `karate.proceed()`. The point is that a consumer cannot otherwise tell a stand-in from the real system — an address proves nothing, since real services live on `localhost` too — so the responder says so itself. Read it in one direction only: its presence is certain, its absence proves nothing, because any other stub answers exactly like a real server. It is suppressed from the Java API rather than from the feature file, for the case where a mock has to imitate a real service's header set byte for byte. Karate 1.x adds no such header, so a test that asserts on the exact header set will see a difference between the lines.

  • A browser calling a CORS-enabled Karate mock reports "blocked by CORS policy", but the Background does have `configure cors = true`. What is the most likely cause?
    No scenario matched the request. The unmatched-request 404 is built outside the success path, so it carries no `Access-Control-Allow-Origin` header and the browser reports the missing header rather than the 404. Look for the handler's "no scenarios matched, returning 404" log line and fix the matcher, not the CORS setting.
  • With `configure cors = true` in the Background, can you still write your own `OPTIONS` scenario?
    No. The preflight short-circuit runs before scenario matching, so an `OPTIONS` request is answered by the handler and your scenario is never consulted. If you genuinely need to control the preflight reply, leave CORS off and let a scenario match `methodIs('options')` so you can set the headers yourself.
  • How do you stop one endpoint carrying a header that `configure responseHeaders` sets for everything else?
    You cannot, from the scenario. The scenario map is merged over the configured map, so it can only give a key a different value, never remove it. If a header must be absent on some route, take it out of the configured map and set it explicitly in the scenarios that do want it.

saying these in an interview costs you the question

  • Says a scenario's responseHeaders replaces the whole configured header map.
  • Puts configure cors or configure responseHeaders inside a scenario body.
  • Assumes the CORS header is added to the unmatched-request 404 as well.
  • Thinks an OPTIONS scenario still runs while configure cors is switched on.
  • Confuses the responseHeaders variable with the configure responseHeaders setting.