skip to content

In a Karate feature file, `* def data = { foo: [1, 2, 3] }`. How do `match data.foo contains [3, 2]`, `match data.foo contains only [3, 2, 1]` and `match data.foo contains any [9, 2, 8]` differ?

level: middleimportance: should knowfreq 50%

answer

  1. three degrees of accounting
  2. none of the three cares about order
  3. one of them checks array length
  4. one of them exits on the first hit
  5. duplicates need visited bookkeeping

basics

~20 s

contains needs every expected item present and lets the actual array be longer. contains only needs the same items in any order and the same length, so a shorter expected list fails. contains any needs at least one expected item.

solid answer

~40 s

The three differ in how much of the actual array they account for. `contains [3, 2]` passes: every expected item must be found somewhere, order is irrelevant and the actual array may be longer. `contains only [3, 2, 1]` also passes, but it additionally requires the two arrays to be the **same length** — `contains only [2, 3]` against a three-element array fails with `actual array length is not equal to expected`. `contains any [9, 2, 8]` passes on the strength of `2` alone, because one match is enough and the check exits early. All three work on JSON objects too, where `contains only` means the same key set and `contains any` means at least one key-value pair matched.

code

gherkin · 16 lines
gherkin
* def data = { foo: [1, 2, 3] }

* match data.foo contains [3, 2]
* match data.foo contains only [2, 3, 1]
* match data.foo contains any [9, 2, 8]

# fails - 'contains only' requires equal lengths
# * match data.foo contains only [2, 3]

# duplicates are counted by 'contains only'
* def dupes = [2, 3, 2]
* match dupes contains only [2, 2, 3]

# objects work too: at least one key-value pair must match
* def obj = { a: 1, b: 'x' }
* match obj contains any { b: 'x', c: true }

go deeper

for a junior

Learn the three one-liners: contains is a subset, contains only is the same items in any order, contains any needs just one. Being able to pick the right word for a given payload is the goal here.

for a middle

Explain the extra length check that contains only performs, the early exit that contains any takes, and how visited bookkeeping makes duplicate handling correct.

for a senior

Notice when contains any has been used to silence a flaky list assertion rather than to model a real choice of values — it can pass on a single coincidental match and reports nothing about which.

for a principal

Agree across a suite when a collection is treated as closed and ordered, closed and unordered, or open. Each answer maps to a different operator, and mixing them freely makes review guesswork.

## Three degrees of accounting `contains`, `contains only` and `contains any` are the same walk with three different answers to one question: *how much of the actual value has to be accounted for?* | operator | every expected item present? | actual may hold extras? | length must match? | |---|---|---|---| | `contains` | yes | yes | no | | `contains only` | yes | no | yes | | `contains any` | no — one is enough | yes | no | None of the three cares about **order**. That is the point of the family: a service that returns a list in an unspecified order can still be asserted precisely, without sorting the payload first and without a comparator. All three also work unchanged on a JSON object, where "item" means "key-value pair", and all three compose with `each`. The `!` negation prefix is the exception: only plain `contains` has a negated form, and there is no `!contains only` and no `!contains any` anywhere in the operator set. Worse, the two lines reject them differently. On Karate 1.x the step still parses and the `!` is **silently dropped** — the parser reads `only` and `any` before it ever looks at the negation — so `match foo !contains only [...]` runs as `contains only`, the exact opposite assertion, passing quietly. On 2.x the expression is not a recognised operator at all and the step is rejected outright. ## `contains` — a subset ```gherkin * def data = { foo: [1, 2, 3] } * match data.foo contains 1 * match data.foo contains [2] * match data.foo contains [3, 2] ``` Each expected element is searched for across the whole actual array. A guard runs first: if the expected list is longer than the actual list the step fails immediately with `actual array length is less than expected`. Within that guard, plain `contains` does not consume the element it matched, so a repeated expected item can be satisfied twice by the same actual element. ## `contains only` — a permutation ```gherkin * match data.foo contains only [3, 2, 1] * match data.foo contains only [2, 3, 1] # this fails - lengths differ # * match data.foo contains only [2, 3] ``` `contains only` adds a length equality check up front, and then marks each actual element as **visited** as soon as it is matched. That visit bookkeeping is what makes it correct in the presence of duplicates: - `[1, 2, 2]` `contains only` `[2, 2, 1]` — passes, the two `2`s consume two distinct elements. - `[1, 2, 3]` `contains only` `[2, 2, 3]` — fails, there is only one `2` to consume. - `[2, 3, 2]` `contains only` `[2, 2, 3]` — passes, both `2`s find a home. Effectively `contains only` asserts that the two arrays are the same **multiset**. Applied to a JSON object it means the same key set: an actual object with more keys than the expected one fails with `actual has N more key(s) than expected`. ## `contains any` — at least one ```gherkin * match data.foo contains any [9, 2, 8] * def obj = { a: 1, b: 'x' } * match obj contains any { b: 'x', c: true } ``` `contains any` is the only member of the family that returns as soon as it succeeds. The first expected element that is found anywhere in the actual array ends the comparison, and the length guard that the others apply is skipped, so the expected list may legitimately be longer than the actual one. Against an object, the first expected key-value pair that matches ends it the same way — which is why `{ a: 1, b: 'x' }` satisfies `contains any { b: 'x', c: true }` despite having no `c` at all. The flip side is that `contains any` is a weak assertion by construction. A three-element expected list against a three-element actual array can pass on a single coincidental match, and there is no report of which one matched. Prefer it where the service genuinely offers a choice of values, not as a way to quieten a list assertion that keeps failing. ## Choosing between them 1. Use `contains` when the service may add elements you do not care about. 2. Use `contains only` when the set is complete and closed but the order is not promised — this is the common case for a collection endpoint whose sort order is undefined. 3. Use `contains any` only when any one of several acceptable values is genuinely correct. A useful review heuristic: if the step would still pass after the service silently dropped an element, ask whether that is really acceptable. `contains` and `contains any` both survive a dropped element in some form; `contains only` does not, which is exactly why it is the strictest of the three and the one worth defaulting to for a closed collection. There is one deeper variant to know about: `contains only deep` behaves like `contains only` but carries the order-insensitivity into every nested array as well, so a payload of nested lists can be pinned exactly while leaving every ordering free. All of these compose with `each` in the usual way, so `match each response contains any { status: 'OK' }` applies the choice to every element of an array rather than to the array itself, and `match each response contains { id: '#number' }` applies the subset rule element by element.

  • Does `contains only` care about duplicate elements?
    Yes. It marks each actual element as visited once it has been matched, so an expected list with two `2`s needs two distinct `2`s on the actual side. That is why `[1, 2, 3] contains only [2, 2, 3]` fails while `[2, 3, 2] contains only [2, 2, 3]` passes.
  • What does `contains any` mean for a JSON object rather than an array?
    At least one expected key-value pair must be present and match. The comparison exits on the first success, so `{ a: 1, b: 'x' }` satisfies `contains any { b: 'x', c: true }` even though `c` does not exist. It is a deliberately weak assertion and should be used only where several values are genuinely acceptable.
  • Which of the three is safe for an endpoint whose sort order is undefined?
    `contains only`, when the collection is complete and closed. It asserts the same elements with the same multiplicity while ignoring order, so a change in sort order does not fail the test but a missing or extra element still does. `contains` would let extra elements slip through unnoticed.

Think of a shopping basket checked against a list. contains says everything on my list is in the basket, extras welcome. contains only says the basket holds exactly my list, in any order, counted. contains any says at least one thing on my list made it in.

saying these in an interview costs you the question

  • Says contains only just means contains with no extras
  • Thinks contains any needs all listed items present
  • Assumes contains only compares element positions in order
  • Believes contains only works on arrays but never on objects
  • Ignores duplicates when reasoning about contains only