In a Karate feature file, what does the step `* match order == { id: 1, name: 'Billie' }` assert, and what happens if `order` also carries a `role` key?
answer
- one step asserts a whole body
- the comparison recurses into nesting
- strict in both directions
- surplus keys are not tolerated
- objects by key, arrays by index
basics
~20 sKarate's match == compares the entire payload in one recursive step: every key and value must correspond exactly. An extra role key fails the step, because == is a strict whole-payload comparison, not a subset check.
solid answer
~50 s`match` is Karate's built-in assertion — there is no step-definition class behind it and no matcher library to import. The step evaluates the left operand as a variable (optionally followed by a JsonPath or XPath), parses the right operand as a Karate data literal, then walks both sides together: objects key by key, arrays element by element, scalars by value. The comparison is **strict in both directions**, so the actual payload may not carry keys the expected side never named. An `order` of `{ id: 1, name: 'Billie', role: 'admin' }` therefore fails with `actual has 1 more key(s) than expected`, and Karate prints the surplus. Whitespace and key order are irrelevant, and numbers compare by numeric value rather than by Java type. `==` is the assertion that pins a payload; `contains` is the separate operator for subset checks.
code
gherkin · 9 linesFeature: deep equality
Scenario: one step pins the whole payload
* def order = { id: 1, name: 'Billie' }
* match order == { name: 'Billie', id: 1 }
Scenario: a surplus key fails the strict compare
* def bigger = { id: 1, name: 'Billie', role: 'admin' }
* match bigger != { id: 1, name: 'Billie' }go deeper
Recall that one match ... == step asserts the whole payload and that it is strict: a key the expected side never named still fails the step.
Explain the recursion — objects compared by key lookup, arrays by index, scalars by value — and name the three distinct object failures: missing key, wrong value, surplus key.
Know what the strictness buys you in production: a whole-payload == is the assertion that catches a field a service silently added, which a targeted check would sail past.
Weigh the cost of strictness across a suite — every additive change to a shared contract touches every == assertion, so decide deliberately where that pressure is a feature and where it is churn.
## `match` is the assertion, not glue Karate has no step-definition registry and no glue path. A line like `* match order == { id: 1, name: 'Billie' }` is executed by the runtime itself: `match` is a first-class keyword and `==` is its comparison operator. There is no class to write, no matcher library to import, no fluent assertion API to chain. That one step is a complete assertion over an entire document. The step takes two operands. The left one names a variable and may be followed by a JsonPath or XPath, so `match order $.items[0].sku == 'PEN-1'` compares only that slice. The right one is evaluated as a Karate data literal, which is why the unquoted, JSON-ish `{ id: 1, name: 'Billie' }` becomes a real map before the comparison starts — quoting keys is optional. Karate then walks both sides together. ## Recursive, type by type `==` dispatches on the data type of the actual value and recurses into whatever it finds: | actual type | how `==` compares it | |---|---| | object / map | every expected key is looked up in the actual map, then compared recursively | | array / list | lengths are checked first, then element by element, by index | | string | exact string equality | | number | numeric value, so an `Integer` 1000 equals a `BigDecimal` 1000 | | boolean | value | | byte array | byte for byte, which is why `match responseBytes == read('test.pdf')` works | | XML node | converted to a map and walked exactly like JSON | Because the walk recurses, nesting depth costs you nothing. One step pins a five-level document as firmly as a flat one, and the failure report names the level that broke. ## Strict in both directions Three distinct things can go wrong when both sides are objects: 1. **A key the expected side names is absent from the actual payload** — the failure reads `actual does not contain key - 'name'`. 2. **A shared key holds a different value** — the parent line reports `match failed for name: 'name'` and a nested line names the exact path and the two values. 3. **The actual payload carries a key the expected side never named** — `actual has 1 more key(s) than expected`, followed by a printout of just the surplus. The third case is the one candidates get wrong. `==` is not a subset assertion. `{ id: 1, name: 'Billie', role: 'admin' }` fails against `{ id: 1, name: 'Billie' }` even though every key that *was* named matches. That strictness is exactly the point: a whole-payload `==` is the assertion that notices a field the service quietly started returning. ## What deliberately does not matter - **Whitespace and formatting** — the right side is parsed into data, never string-compared. - **Key order inside objects** — expected keys are looked up by name, so a reordered object still passes. - **The Java type of a number** — comparison is by numeric value, not by wrapper class. - **How the payload arrived** — the same engine compares a variable you built, an HTTP response body, and a file read from disk. ## The negated form `!=` is the same comparison, inverted: Karate runs the full recursive equality check and passes when that check fails. `match bigger != { id: 1, name: 'Billie' }` passes precisely because the surplus `role` key made `==` fail. The corollary is that `!=` only ever tells you the two payloads differ somewhere — never where. It is a weak assertion by construction, useful for pinning "this is definitely not the stale record" and little else. ## When the path resolves to nothing If the left operand carries a path that matches no node, the failure is `actual path does not exist` rather than a value mismatch. That distinction is worth recognising in a report: it means the shape of the document changed, not that a value drifted. ## The same engine from JavaScript `karate.match('order == { id: 1 }')` and `karate.match(actual, expected)` both run the comparison and hand back a result object carrying a `pass` flag instead of failing the step. That is the escape hatch for the rare case where you need to branch on the outcome rather than assert it. Prefer the string form: it is dispatched through the very evaluator the `match` keyword uses, so it behaves identically. The two-argument form is not quite the keyword. On Karate 2.x it pre-parses an operand that looks like JSON or XML before comparing — something the keyword never does — so `karate.match('{ "id": 1 }', { id: 1 })` reports a pass while the equivalent `* def raw = '{ "id": 1 }'` and `* match raw == { id: 1 }` fail on `(STRING:MAP)`. On Karate 1.5.2 the two-argument form reaches the same engine entry point the keyword does, and the two agree.
- In Karate, does `match total == 1000` still pass when the payload holds the value as a BigDecimal rather than an int?Yes. Karate compares numbers by numeric value, not by Java wrapper class, so an `Integer` 1000, a `Double` 1000.0 and a `BigDecimal` 1000 are all equal to each other. This matters because a JSON parser's choice of numeric type is an implementation detail you should not have to assert around.
- In Karate, what does the failure say when the expected object names a key the actual payload does not have?You get `actual does not contain key - 'name'` on the line for the enclosing object, together with the path of that object. That is a different message from a value mismatch, which reads `match failed for name: 'name'` and adds a nested line naming the leaf path and printing both values.
- In a Karate feature file, can the left side of `match` be something other than a bare variable name?Yes — the left operand may be a variable followed by a JsonPath or XPath, so `match order $.items[0].sku == 'PEN-1'` compares only that slice with the same recursive engine. If the path resolves to nothing, the failure is `actual path does not exist` rather than a value mismatch.
saying these in an interview costs you the question
- Says == is a subset check that ignores extra keys
- Thinks a match step needs a step definition or glue class
- Believes key order in a JSON object must match
- Assumes == only compares the top level, not nested objects
- Claims a JSON-looking string is auto-parsed before comparison
- Thinks != tells you which field differed