skip to content

In a Karate feature file, what happens when an expected payload contains a marker name Karate does not recognise, such as `'#numbr'` or `'##anythng'`?

level: seniorimportance: nice to knowfreq 31%

answer

  1. the vocabulary is closed, the parser is not
  2. no error is reported at all
  3. it becomes a plain string comparison
  4. the prefix is checked, the name is not
  5. '##notnull' can never fail

basics

~20 s

Nothing reports the typo. An unrecognised single-hash name degrades to a literal string comparison against the actual value, and a double-hash name is skipped outright whenever the key is absent, so the assertion can silently check nothing.

solid answer

~60 s

There is no unknown-marker error. When the name after `#` is not one of the eleven validators and is not `regex`, the engine falls back to treating the whole thing as **an ordinary string that happens to start with `#`** and compares it literally to the actual value. So `'#numbr'` does not report a typo; it asserts that the actual value is the string `"#numbr"`. That normally fails, but never with a message that names the marker: against a non-string actual the fallback casts the value to `String` first, so the step dies with a `ClassCastException`, and against a string actual the comparison merely returns false and the report is the parent's `match failed for name: '<key>'`. The `##` case is worse, because the optional prefix is recognised by prefix alone: on a **missing key** the engine skips any expected value starting with `##` without ever looking the name up, so `'##anythng'` passes. The same short-circuit is why `'##notnull'` can never fail — absent passes, `null` passes, and a real value passes the validator. Grep a suite for `##` and for marker names outside the known set; both defects look like assertions in review.

code

gherkin · 22 lines
gherkin
* def response = { }

# typo behind a double hash: passes, checks nothing
* match response == { total: '##numbr' }

# reads strict, cannot fail on any payload
* match response == { total: '##notnull' }

* def actual = { total: 7 }

# single-hash typo against a number: the fallback casts the actual to String first,
# so the step dies with a ClassCastException, not a marker error
# * match actual == { total: '#numbr' }

* def actualStr = { total: 'seven' }

# against a string actual it merely fails: match failed for name: 'total'
# * match actualStr == { total: '#numbr' }

# the fallback exists for this: a value that really does start with a hash
* def tag = '#bob'
* match tag == '#bob'

go deeper

for a junior

Learn the eleven validator names well enough to spot one that is not on the list, because nothing in the tool will tell you a marker name was mistyped.

for a middle

Explain the two fallbacks: an unknown single-hash name becomes a literal string comparison, and any double-hash value is skipped by prefix when the key is absent.

for a senior

Read '##' as a coverage decision in review, and be suspicious of a marker-dense suite that has never gone red for the field in question.

for a principal

Set the expectation that an assertion is only trusted once it has been seen to fail, since a vocabulary with a silent fallback makes green a weak signal at scale.

## There is no unknown-marker error The marker vocabulary is closed — eleven validator names plus the `#regex` prefix — but the engine does not treat an unknown name as a mistake. It has to be forgiving, because a payload may legitimately contain a string that begins with `#`, and there is no way to tell that apart from a typo. So the fallback is deliberate: **if the name is not a known validator, the expected value is compared as a literal string.** That behaviour is pinned by the project's own tests, which assert that matching the actual value `"#bob"` against the expected value `"#bob"` passes. It is correct for the case it exists for, and quietly wrong for the case you hit in practice. ## What a single-hash typo actually does Write `'#numbr'` where you meant `'#number'` and the assertion becomes "this value is the string `#numbr`": - Against a real number, the step **fails** — but never with a marker-aware message, and never with a clean assertion diff either: the fallback runs `String actualValue = actual.getValue()` on a value that is not a string, so the step dies with a `java.lang.ClassCastException`. Where the actual value genuinely is a string, the literal comparison simply returns false and the report is the parent's `match failed for name: '<key>'` with the actual and expected values printed side by side. Either way the diagnosis points at the value, never at the marker — the words "not equal" never appear, because a `#`-prefixed expected value is dispatched to the marker branch before the equality branch that emits them. - Under a `contains` comparison on a string value, the fallback is a substring test rather than equality, which widens the ways it can pass by accident. - If the actual value genuinely is that string, it **passes**. Rare in a real API, entirely possible against a stub or a fixture built by hand. ## What a double-hash typo does This is the one that costs you coverage. When a key is missing from the actual payload, the map walk checks the expected value before descending, and lets three cases through: 1. the expected value is exactly `'#ignore'`; 2. the expected value is exactly `'#notpresent'`; 3. the expected value **starts with** `##`. Only the third is a prefix test. The name after `##` is never looked up, so `'##anythng'`, `'##strng'` and `'##whatever'` all pass on an absent key. The project's own test suite pins this case with a comment flagging it as not really correct — it is known behaviour, not a bug you will see fixed under you, and it means a `##` marker plus a typo plus a dropped field is a green test asserting nothing. Note the asymmetry: `'#ignore'` and `'#notpresent'` are matched by **exact** equality in that branch, so `'#ignor'` is not skipped and the step fails on the missing key. Single-hash typos are noisy — even when the noise is a cast error rather than an assertion diff; double-hash typos are silent. ## The assertion that can never fail `'##notnull'` deserves its own mention, because it reads like the strictest marker in the set and is the loosest: | payload | what happens | |---|---| | key absent | skipped by the `##` prefix — pass | | `{ a: null }` | the optional prefix short-circuits on `null` — pass | | `{ a: 1 }` | the `notnull` validator runs and passes | Every path passes. A field covered by `'##notnull'` is not covered at all, and the diff that introduced it looked like an assertion being added. ## The opposite trap `#regex` fails in the other direction, and is worth knowing alongside these: - The pattern is matched against the **whole** value, not searched within it, so `'#regex hello'` fails on `'hello1'`. - Escaping is doubled, because the expected payload is parsed before the match engine sees it: a literal dot is `'#regex a\\.dot'`. - A non-string value fails with `not a string` before the pattern is applied at all. These produce confusing red builds rather than false green ones, which is the better failure mode — but the confusion is the same shape: the marker did not do what the text implied. ## What to do about it - **Review `##` deliberately.** Treat every `##` as a decision to stop checking presence, and ask whether it was one. - **Never write `'##notnull'`.** Use `'#notnull'` if the field is required, or `'##string'` and friends if it is genuinely optional. - **Sweep for unknown names.** The vocabulary is small enough to grep: any `'#word'` in an expected payload that is not one of the eleven, `#regex`, an array shortcut or a predicate is a typo. - **Prove an assertion can fail.** The cheapest check on a marker-heavy suite is to break the payload once and confirm the step goes red. An assertion that has never failed has never been tested.

  • Why does Karate compare an unrecognised `#`-marker as a literal string instead of raising an error?
    Because a payload may legitimately hold a string beginning with `#`, and nothing distinguishes that from a typo at match time. Failing hard on any unknown name would make those values unassertable. The fallback is the deliberate choice, and its own test pins the passing case. The cost is that a mistyped validator name is indistinguishable from a value that happens to look like one.
  • In a Karate feature file, how would you find marker assertions that cannot fail?
    Grep the expected payloads for `##` and review each as a decision to stop checking presence; `'##notnull'` in particular passes on every payload. Then check any `'#name'` against the known vocabulary — the eleven validators plus `#regex` — since anything else is being compared as a literal string. The confirming test is to break a payload deliberately and watch the step go red.

saying these in an interview costs you the question

  • Expects an 'unknown validator' error for a mistyped marker
  • Thinks Karate validates the marker name behind a '##' prefix
  • Says '##notnull' asserts the value is present and not null
  • Assumes a single-hash typo fails with a readable assertion diff rather than a cast error
  • Believes '#ignor' is skipped the way '#ignore' is
  • Says a suite is covered because every key carries some marker