skip to content

In a Karate feature file, how do the expected values `'#null'`, `'#notpresent'`, `'#present'` and `'#notnull'` differ, and which of them passes when the key is missing from the response?

level: middleimportance: must knowfreq 62%

answer

  1. presence first, value second
  2. absent is not the same as null
  3. which one tolerates a missing key
  4. the double-hash escape hatch
  5. '#notpresent' asserts a key is absent

basics

~10 s

'#null' needs the key present holding null. '#notnull' needs it present and not null. '#present' needs it present, any value including null. '#notpresent' needs it absent. Only '#notpresent' passes on a missing key.

solid answer

~50 s

Karate's `match` treats *a key that exists holding `null`* and *a key that is not in the payload at all* as two different states, and there is a marker for each. `'#null'` asserts the key is present and its value is `null`; `'#notpresent'` asserts the key is absent; `'#present'` asserts the key exists but accepts any value, `null` included; `'#notnull'` asserts it exists and is not `null`. Only `'#notpresent'` passes on a missing key — `'#null'`, `'#present'` and `'#notnull'` all fail, because a marker cannot validate a value that is not there. Two expected values escape that rule: `'#ignore'`, which passes on anything, and the `##` prefix, which makes the whole assertion optional — `'##null'` passes on an absent key, on `null`, and fails only on a real value. `'#notpresent'` is also useful inside an `==` match to assert a field was *not* returned.

code

gherkin · 19 lines
gherkin
* def absent = { }
* match absent == { a: '#notpresent' }
* match absent == { a: '#ignore' }
* match absent == { a: '##null' }
* match absent == { a: '##notnull' }

* def isNull = { a: null }
* match isNull == { a: '#null' }
* match isNull == { a: '#present' }
* match isNull == { a: '##null' }

* def hasValue = { a: 1 }
* match hasValue == { a: '#notnull' }
* match hasValue == { a: '#present' }

# these fail
# * match absent  == { a: '#null' }
# * match isNull  == { a: '#notnull' }
# * match hasValue == { a: '#notpresent' }

go deeper

for a junior

Learn the four names as a pair of pairs: '#null' and '#notnull' talk about the value, '#present' and '#notpresent' talk about the key itself.

for a middle

Be able to draw the grid from memory and say which three expected values survive an absent key: '#notpresent', '#ignore' and anything prefixed '##'.

for a senior

Notice in review when '#notnull' has drifted to '##notnull' — the second can never fail, so the field stops being covered while the diff still looks like an assertion.

for a principal

Decide as a standard whether your services strip null keys or serialise them, because that single choice determines whether suites should be written with '#' or '##' throughout.

## Two states, not one Most assertion styles collapse "the field is null" and "the field isn't there" into a single falsy check. Karate's `match` keeps them apart, and the marker set has a name for each corner of the grid. That distinction is not pedantry: for a JSON API it is the difference between a server that explicitly reported "no value" and one that dropped the field — a rename, a serialisation change or a partial response. The four markers in question read almost as English, which is precisely why they get confused. Each one asserts something about **presence** first and value second. - **`'#null'`** — the key must be in the payload, and its value must be `null`. - **`'#notnull'`** — the key must be in the payload, and its value must be anything other than `null`. - **`'#present'`** — the key must be in the payload. Any value passes, `null` included. - **`'#notpresent'`** — the key must *not* be in the payload at all. ## The whole grid Three payload states across the markers, including the two escape hatches: | expected value | `{ }` — key absent | `{ a: null }` | `{ a: 1 }` | |---|---|---|---| | `'#null'` | fail | **pass** | fail | | `'#notnull'` | fail | fail | **pass** | | `'#present'` | fail | **pass** | **pass** | | `'#notpresent'` | **pass** | fail | fail | | `'#ignore'` | **pass** | **pass** | **pass** | | `'##null'` | **pass** | **pass** | fail | | `'##notnull'` | **pass** | **pass** | **pass** | Read the first column on its own: of every expected value in the marker vocabulary, only three survive a key that is not in the payload — `'#notpresent'`, `'#ignore'`, and anything beginning with `##`. Every other marker, `'#string'` and `'#number'` included, fails with `actual does not contain key` before its own type check ever runs. ## Why `##` behaves the way it does The `##` prefix means *optional*, and it is broader than the name "optional key" suggests. It covers three cases at once: 1. the key is **absent** from the payload; 2. the key is present but holds **`null`**; 3. the key is present with a real value that the rest of the marker validates. So `'##string'` reads as "absent, null, or a string". This matches a convention many services adopt of stripping null-valued keys from a response — with `'##string'` the assertion passes whichever of the two shapes you get. The cost is that `'##notnull'` is a contradiction the engine happily accepts: absent passes, `null` passes, and a real value passes, so that particular combination can never fail. ## `'#notpresent'` as a positive assertion Because `match ... ==` is an exact comparison, the usual way to say "this field must not come back" is to leave it out of the expected payload — a surplus key in the response then fails the step. `'#notpresent'` lets you say it *explicitly*, inside the same object as everything else: ```gherkin * def response = { id: 1, name: 'Billie' } # id and name checked, and password proven absent, in one step * match response == { id: '#number', name: '#string', password: '#notpresent' } ``` That reads better in review than an omission, and it survives someone later switching the step to a looser comparison, where a merely-omitted key would stop being checked at all. ## Where each one earns its place - **`'#present'`** is for a key whose value you genuinely cannot constrain — an opaque token, a vendor blob — but whose *existence* is part of the contract. - **`'#notnull'`** is the everyday choice for "there must be a real value here", and is stricter than `'#present'` by exactly one case. - **`'#null'`** pins a deliberate null, such as an `endedAt` on a record that is still open. - **`'#notpresent'`** pins an absence, and is the one to reach for on fields that must never be serialised outward. A last mechanical note: on a missing key the engine recognises `'#ignore'` and `'#notpresent'` by **exact** string equality, while the `##` case is recognised by prefix. A typo such as `'#notpresnt'` therefore is not treated as a marker at all, and the step fails on the missing key with a message that never mentions the typo. ## Presence markers need a payload context `'#present'` and `'#notpresent'` are statements about a **key**, so they only mean something where there are keys to talk about: inside a JSON or XML payload, or with a JsonPath or XPath on the left of the match. Both spellings work and they are worth knowing as a pair: ```gherkin * def json = { foo: 'bar' } * match json == { foo: '#present' } * match json.nope == '#notpresent' ``` The second form is how you assert a single absence without writing out the rest of the object. The first is the one to prefer when you can, because the surrounding `==` keeps every other key under assertion at the same time — checking one path in isolation says nothing about what else the response grew since the test was written.

  • In a Karate feature file, your response drops null-valued keys entirely. Which marker keeps one assertion working for both shapes?
    The `##` prefix. `'##string'` passes when the key is absent, when it is present holding `null`, and when it holds a string, so the same expected payload matches a service before and after it starts stripping nulls. Use the plain `'#string'` when the key must always be serialised — `##` deliberately gives up the presence check, and that is the trade you are making.
  • In a Karate feature file, what is the difference between omitting a key from the expected payload and writing `'#notpresent'` for it?
    Under an exact `==` match they land in the same place: a surplus key in the response fails the step either way. `'#notpresent'` states the intent explicitly, so a reviewer sees the field was considered rather than forgotten, and the assertion still holds if the comparison is later loosened, where a merely-omitted key would no longer be checked at all.

saying these in an interview costs you the question

  • Treats a null value and an absent key as the same failure
  • Says '#null' passes when the key is missing
  • Thinks '#present' rejects a null value
  • Believes '#notpresent' is only for the left-hand side of a match
  • Describes '##' as covering an absent key but not a null one
  • Assumes '#string' tolerates a missing key the way '#ignore' does