skip to content

A Karate mock has `Scenario: pathMatches('/cats') && paramValue('type')`, but a request to `/cats?type=tabby` falls through to the catch-all instead. Why?

level: middleimportance: should knowfreq 40%

answer

  1. JavaScript truthiness is not enough
  2. Logical and returns its right operand
  3. Two helpers hand back values
  4. A thrown matcher is only a warning
  5. Every matcher mistake looks identical

basics

~20 s

The expression evaluates to the string tabby, not to true. A Karate mock selects a scenario only when its name evaluates to a real boolean true, so a truthy string is a non-match. Compare the value instead, or use paramExists.

solid answer

~50 s

JavaScript's `&&` returns its right-hand operand, so this name evaluates to the string `'tabby'` — truthy in JavaScript, but not a boolean. The mock handler is stricter than the language: it selects a scenario only if the name evaluates to a genuine boolean `true`, and a string, number, object or `null` is treated as *did not match*. Fix it by comparing (`paramValue('type') == 'tabby'`), by using the boolean helper (`paramExists('type')`), or by coercing (`!!paramValue('type')`). The same strictness covers failure: any exception thrown while a name is being evaluated is caught, logged as a warning and treated as a non-match, so a typo like `pathMatchs('/cats')` or a call to a helper that this version does not have will silently fall through to the next scenario and, eventually, to the handler's own 404. **A matcher never produces a 500** — that only comes from a step inside a scenario that did match.

code

gherkin · 7 lines
gherkin
# never matches: the expression evaluates to the string 'tabby'
Scenario: pathMatches('/cats') && paramValue('type')
* def response = { wrong: true }

# matches: the expression evaluates to a boolean
Scenario: pathMatches('/cats') && paramValue('type') == 'tabby'
* def response = { breed: 'tabby' }

go deeper

for a junior

Take away one rule: a matcher must evaluate to true or false, so compare values instead of leaning on them being non-empty.

for a middle

Explain why the logical and yields a string here and why the handler rejects it, then give both fixes — an equality test or the boolean helper.

for a senior

Recognise the shared symptom: typo, missing helper and truthy value all present as a stub that quietly stops answering, with only a warning in the mock's log.

for a principal

Decide how much conditional logic belongs in scenario names at all, given that a wrong one fails silently and no build step can catch it.

## Truthy is not true The name of a mock scenario is evaluated as JavaScript, but the handler does not apply JavaScript truthiness to the result. It asks a narrower question: *is this value the boolean `true`?* Anything else — a non-empty string, a non-zero number, an object, `undefined`, `null` — is recorded as a non-match and the walk moves on to the next scenario. That is why the name in the question fails. `&&` is not a boolean operator in JavaScript; it returns the last operand it evaluated. With `pathMatches('/cats')` true, the value of the whole expression is whatever `paramValue('type')` returned, which is the string `'tabby'`. A person reading the file sees a condition that is obviously satisfied; the handler sees a string and skips the block. ## The two helpers that hand you a value Only two of the mock helpers return data rather than a verdict, and both walk into this: - `paramValue(name)` — the first value of a query or form parameter, or `null` when it is absent; - `bodyPath(expr)` — a value pulled from the request body by JsonPath or XPath, or `null` when the path does not resolve. Every other helper — `pathMatches`, `methodIs`, `typeContains`, `acceptContains`, `headerContains`, `paramExists` — returns a boolean and composes safely. ## Three ways to fix it 1. **Compare the value**, which is usually what you meant anyway: `pathMatches('/cats') && paramValue('type') == 'tabby'`. This also documents the route. 2. **Use the boolean helper** when presence is the condition: `pathMatches('/cats') && paramExists('type')`. 3. **Coerce**, when you genuinely want "any non-empty value": `pathMatches('/cats') && !!paramValue('type')`. The same applies to bodies: write `bodyPath('$.name') == 'Billie'`, not `bodyPath('$.name')`. ## Failure is also a non-match The second half of the mechanism is what happens when a name **throws**. The handler wraps the evaluation: an exception is caught, logged as a warning naming the line and the expression, and turned into `false`. Nothing is returned to the client on account of a broken matcher, and the request simply continues down the file. That makes the failure mode quiet in a specific way worth knowing: - a misspelled helper (`pathMatchs`, `methodIS`) produces a scenario that never fires; - a helper that exists in one Karate line but not the other behaves the same way — the stub silently stops answering after an upgrade or a downgrade; - a name that reads a field of something that is absent — `request.order.id == 7` on a request whose body has no `order` — throws and is swallowed too; - a `null`-returning helper feeding a property access, such as `paramValue('type').length > 2` with the parameter absent, is the same story. The only visible trace of all of these is a warning line in the mock's own log, and then a reply from whichever scenario matched next — a neighbouring stub, the catch-all, or the handler's bare 404. ## How to debug it Because the evidence lives in the log rather than in the response, the routine is: 1. Read the mock's log for the warning about a failed match evaluation; it names the line and prints the expression. 2. If there is no warning, the expression did not throw — it returned something that was not `true`. Suspect a value-returning helper used bare. 3. Move the doubtful condition out of the name and into the body temporarily: match broadly, `* def x = paramValue('type')` and `* def response = x`, and look at what actually arrives. 4. Keep a catch-all last that echoes `requestMethod` and `requestUri` in its body, so a routing miss reports itself instead of arriving as an empty 404. ## Why the handler is strict Requiring a real boolean is not an oversight. A matcher is chosen before anything else happens, and JavaScript truthiness would make several common half-written conditions match by accident — a bare `paramValue()` that returned any value at all, an object, a non-empty string that came straight from the client. Rejecting everything but `true` means a routing decision is only ever taken on an expression that actually decided something. ## Why this comes up in interviews It separates "I have written a mock feature" from "I have debugged one". The behaviour is defensible — a matcher that throws should not take down the mock, and requiring a real boolean stops a half-written condition from matching by accident — but it means **every mistake in a matcher has exactly the same symptom**: the stub does not answer. Knowing that the symptom is common to a typo, a version-only helper and a truthy-but-not-true value is what makes the difference in a live debugging session.

  • What does a Karate mock return to the client when a scenario name throws while being evaluated?
    Nothing of its own. The exception is caught, logged as a warning against that line, and treated as a non-match, so evaluation continues with the next scenario and the client gets whatever matches later — often the catch-all, or the handler's 404 if nothing does. A 500 from a mock always comes from a step inside a scenario that already matched.
  • How would you make a Karate mock scenario match only when a query parameter is present and non-empty?
    `paramExists('type')` covers presence, but it is true for `?type=` with an empty value as well. For present-and-non-empty, compare or coerce: `paramValue('type') != null && paramValue('type') != ''`, or the shorter `!!paramValue('type')`. What you must not write is a bare `paramValue('type')`, which yields a string rather than a boolean.

The handler asks a yes-or-no question and accepts only the words yes or no. Answer with a cat's name, however meaningful, and it hears neither.

saying these in an interview costs you the question

  • Assumes JavaScript truthiness selects the scenario
  • Expects a broken matcher to return 500
  • Thinks a logical and always yields a boolean
  • Looks for the failure in the HTTP response, not the log
  • Uses bodyPath as a bare condition