skip to content

In a Karate feature file, what does `match response contains { id: 1 }` assert, and what does `match response.ids contains 23` assert when `ids` is a JSON array?

level: juniorimportance: must knowfreq 76%

answer

  1. a subset check, not a whole-payload check
  2. extra keys on the actual side are ignored
  3. arrays: membership, order irrelevant
  4. a bare scalar is wrapped into a list
  5. plain strings fall back to substring

basics

~20 s

Karate's contains asserts a subset. Against a JSON object, every expected key must exist with an equal value while extra actual keys are ignored. Against a JSON array, every expected item must appear somewhere, in any order.

solid answer

~40 s

`match response contains { id: 1 }` asserts only that the response object has an `id` key equal to `1`; any other key the service returns is never looked at, which is exactly what separates it from `match response == { id: 1 }`. Against a JSON array, `match response.ids contains 23` is a membership test: every expected item must be found somewhere in the actual array, order does not matter, and the actual array may be longer. Karate wraps a bare scalar on the right into a one-element list, so `contains 23` and `contains [23]` behave identically. Against a plain string, `contains` degrades to an ordinary substring test. An empty expected object, `contains {}`, always passes.

code

gherkin · 23 lines
gherkin
* def cat =
  """
  {
    name: 'Billie',
    kittens: [
      { id: 23, name: 'Bob' },
      { id: 42, name: 'Wild' }
    ]
  }
  """

# object: only the named keys are checked
* match cat contains { name: 'Billie' }

# array: order and size of the actual array do not matter
* match cat.kittens[*].id contains 23
* match cat.kittens[*].id contains [42, 23]

# whole objects can be looked for inside an array
* match cat.kittens contains { id: 42, name: 'Wild' }

# an empty expected object asserts nothing at all
* match cat contains {}

go deeper

for a junior

Recall that contains checks a subset: named keys must be there, unnamed keys are ignored, and array order does not matter. Being able to write the step and say what it does not check is enough at this level.

for a middle

Explain the mechanics: the walk is driven by the expected side, a scalar on the right is auto-wrapped into a one-element list, and a string actual falls back to a substring test.

for a senior

Watch for the assertions that quietly stop asserting — an expected object that collapses to {}, or a one-character expected string on a string actual. Both stay green forever.

for a principal

Set the house convention for when a suite reaches for contains versus ==, and make sure the convention is written down, because the two read almost identically in review.

## The two comparison families Karate's `match` is built into the language — there is no step-definition to write, no matcher library to import and no assertion object to build, so the choice between "compare the whole payload" and "compare a subset" is a single word inside the step. `match x == y` is a recursive deep-equality check in which the expected literal has to describe the entire value. `match x contains y` relaxes that at the level the operator is applied to: the step states what must be present and stays silent about everything else. ```gherkin * def foo = { bar: 1, baz: 'hello', ban: 'world' } * match foo contains { bar: 1 } * match foo contains { bar: 1, baz: 'hello' } # this would fail - '==' has to describe the whole object # * match foo == { bar: 1, baz: 'hello' } ``` ## JSON objects: a subset of keys With a map on the left, `contains` walks the **expected** map, not the actual one. Every key in the expected object must exist on the actual side and its value must match; keys the actual object carries that the expected object never names are never examined. - `match foo contains { bar: 1 }` passes even though `foo` also holds `baz` and `ban`. - `match foo contains { huh: 1 }` fails with `actual does not contain key - 'huh'`. - `match foo contains { bar: 2 }` fails on the **value**, not on the key. - `match foo contains { }` **always passes**. An empty expected map asserts nothing whatsoever, which is easy to produce by accident when the expected object is built from a variable or read from a file that came back empty. ## JSON arrays: membership, not position With a list on the left, `contains` is a membership test. Each expected element is searched for across the whole actual array, so **order is irrelevant** and the actual array is allowed to be longer than the expected one. | step, against `[23, 42, 7]` | result | |---|---| | `match ids contains 23` | passes | | `match ids contains [42, 23]` | passes — order does not matter | | `match ids contains [23, 42, 7]` | passes | | `match ids contains [23, 99]` | fails — `99` is nowhere in the array | Two mechanical details fall out of how that search is written: 1. A length guard runs **before** the search. If the expected list is longer than the actual list the step fails immediately with `actual array length is less than expected`. 2. Inside that guard, plain `contains` does not track multiplicity — a repeated expected item can be satisfied by the same actual element more than once. Only `contains only` marks an actual element as consumed once it has been matched. Elements are compared as whole values, so an array of objects behaves the same way: `match cat.kittens contains { id: 42, name: 'Wild' }` finds that object at any index. ## Strings, XML and the scalar shortcut When the actual value is a plain string, `contains` becomes an ordinary substring test — `match name contains 'ali'` passes for `'alice'`, and nothing JSON-ish happens. When the actual value is XML, both sides are converted to a map first, so the object rules above apply to it. When the two sides have **different** types and the operator belongs to the `contains` family, Karate wraps a non-list expected value into a one-element list before comparing. That is why `match ids contains 23` and `match ids contains [23]` are the same assertion, and why `match cat.kittens[*].name contains 'Bob'` reads naturally after a JsonPath wildcard. The wrap is deliberately skipped for expected strings that stand for an array, an object or an embedded expression — anything beginning `#[`, `##[`, `#(` or `##(`, plus `#array` and `#object` — so a marker on the right is still interpreted as a marker rather than smuggled in as a literal. ## Where the looseness stops `contains` is lenient **only at the level it is applied to**. The moment the walk descends into a nested object or array, the child comparison switches back to equality — which is the entire reason `contains deep` exists as a separate operator. The `each` modifier composes with the family too: `match each response contains { id: '#number' }` applies containment to every element of an array rather than to the array itself, and `!contains` negates the whole result. Because all of this lives in the step grammar, `Given`, `When`, `Then` and `*` are interchangeable labels in front of it — `* match ...` is as valid as `Then match ...`, and neither form is resolved against a glue registry.

  • What does `match foo contains { }` assert about `foo`?
    Nothing. An empty expected map has no keys to walk, so the step passes for any object. It is worth guarding against, because an expected payload built from a variable or read from a file can collapse to `{}` and turn a real assertion into a no-op that still shows green.
  • Does `match name contains 'ali'` work when `name` is the string `'alice'`?
    Yes. When the actual value is a string, `contains` is a plain substring test, so it passes. That also means a typo such as `match name contains 'alicee'` fails on containment rather than on a type error, and that a one-character expected string will match far more strings than you intended.
  • Is `match ids contains 23` different from `match ids contains [23]`?
    No. When the two sides have different types and the operator is in the `contains` family, Karate wraps the non-list expected value into a one-element list first, so the two steps run the identical comparison. The wrap is skipped for expected strings that denote an array, an object or an embedded expression.

saying these in an interview costs you the question

  • Says contains also requires both arrays to be the same length
  • Believes contains on a JSON array is order-sensitive
  • Thinks contains checks nested objects loosely as well
  • Claims contains only works on objects and never on arrays
  • Assumes match foo contains {} fails on a non-empty object