A Karate step `* match order == expected` fails on a deeply nested JSON body. What does Karate's `match failed:` report give you beyond "the two payloads differ"?
answer
- the output has a shape worth knowing
- one block per level of nesting
- indentation tracks depth from the root
- the deepest block names the leaf
- the parentheses carry the two types
basics
~20 sKarate prints the match type, then one indented block per level of the failure path: the JsonPath, the reason, the two data types, and the actual and expected values at that node, ending at the exact leaf that differed.
solid answer
~50 sThe report is a path trail, not a diff. The first line names the comparison type — `match failed: EQUALS`. Then Karate walks down from the root, printing one block per level: the path at that level, the reason the level failed, the two data types in parentheses, and the actual and expected values as they stood at that node. Each level is indented one step further, so the last and deepest block names the exact leaf — something like `$.a.b.c | not equal (NUMBER:NUMBER)` followed by the two scalars. XML follows the same shape with slash-separated paths rooted at `/`. The value is that you do not have to eyeball two large documents: the report already tells you the path, the kind of mismatch, and whether the two sides were even the same data type.
code
gherkin · 5 linesFeature: reading a match failure
Scenario: the report names the exact path
* def order = { a: { b: { c: 1 } } }
* match order == { a: { b: { c: 2 } } }go deeper
Know that the failure names a path rather than just printing two documents, and that the most indented block is the one to read first.
Explain the four parts of a block — path, reason chain, type pair, then the two values — and how indentation maps to depth from the document root.
Use the report as a triage tool: type pair first to separate a shape change from a value drift, then the leaf path, then upward only if the leaf is surprising.
Judge an assertion library partly by what it prints on failure. Diagnosis cost per red build is a real operating expense across a large suite estate, and it compounds with every team that inherits the output.
## The report is a trail, not a diff When a `match ... ==` fails, Karate does not hand you two blobs and leave you to compare them. It records a failure at every level of the recursion that contributed to the outcome, then renders those levels from the root down, indenting one step per level. The result reads as a route from the document root to the value that actually broke. The header line names the comparison type. Every block below it has the same four-part shape: ``` <path> | <reason> (<actualType>:<expectedType>) <actual value at this node> <expected value at this node> ``` A four-level nested mismatch renders like this: ``` match failed: EQUALS $ | not equal | match failed for name: 'a' (MAP:MAP) {"a":{"b":{"c":1}}} {"a":{"b":{"c":2}}} $.a | not equal | match failed for name: 'b' (MAP:MAP) {"b":{"c":1}} {"b":{"c":2}} $.a.b | not equal | match failed for name: 'c' (MAP:MAP) {"c":1} {"c":2} $.a.b.c | not equal (NUMBER:NUMBER) 1 2 ``` ## What each part buys you - **The path.** The deepest block names the offending node exactly. JSON paths are rooted at `$` and use `.name` for keys and `[i]` for indices; a key containing a dash, a space or a dot is bracket-quoted instead, as `$['content-type']`. XML paths are rooted at `/`, slash-separated, and attributes appear as `/hello/@foo`. - **The reason chain.** A parent's line carries both its own reason and the child's summary, joined by a pipe — `not equal | match failed for name: 'c'`. Reading down the chain tells you whether the break was a wrong value, a key the actual payload did not carry, or a surplus key. - **The type pair.** `(NUMBER:NUMBER)` says both sides were numbers and the values differed. `(STRING:NUMBER)` says something quite different: the shapes disagreed, and the failure is `data types don't match`. That parenthesis is often the fastest read in the whole report. - **The narrowed values.** Each level prints only the fragment at that node, so by the time you reach the leaf you are looking at two scalars rather than two thousand-line documents. ## Reading it fast, in practice 1. **Jump to the last block.** It is the most indented and it names the leaf. Nine times out of ten that is the whole answer. 2. **Read the type pair before the values.** A type mismatch means a shape change — a parse that did not happen, a field that became a string, an object that became a list — and it is a different investigation from a value drift. 3. **Read upward only if the leaf surprises you.** The intermediate blocks show the enclosing objects at each step, which is how you tell "the third order in the list" from "the order object at the root". ## Two paths that mean something specific - `actual path does not exist` — the left operand's JsonPath or XPath resolved to nothing. The document's *shape* changed; no value was ever compared. - `actual has N more key(s) than expected` — followed by a printout of only the surplus keys. This is the strictness of `==` firing, and it is usually a service that started returning a new field rather than a bug in the test. ## What the report does not give you It is not a structural diff, and it does not attempt to align two lists that drifted by insertion. If the insertion changed the length you never even reach the elements: the list comparison checks the two sizes first and stops there, with `actual array length is not equal to expected - 4:3` and nothing else. Only when the lengths still agree — a head insertion that pushed the last row off a fixed-size page, say — does the walk descend by index, and then every position from the insertion point on mismatches. Karate 2.x gathers those into one line, `array match failed at indices [0, 1, 2]`, with a nested block per index; Karate 1.5.2 stops at the first and reports `array match failed at index 0`. Read either signal as "the list shifted", not as "many fields changed". Nor does `!=` produce any of this. The negated form passes when the equality check fails and fails when it succeeds, so on failure it can only tell you the two payloads *were* equal. If you want to know where two documents differ, the assertion has to be `==`. ## Why this matters at senior level Assertion output is the interface between a red pipeline and a human under time pressure. A framework that reports "expected X but got Y" over two large documents costs minutes per failure; one that names `$.orders[2].payment.status` and the two scalars costs seconds. Knowing the exact shape of the report — and knowing that the type pair is printed — is the difference between triaging a nightly run and re-running it locally to find out what broke.
- In a Karate match failure, what does a type pair like `(STRING:NUMBER)` in the parentheses tell you?That the two sides were not even the same kind of value, so the reason on that line is `data types don't match` rather than a value comparison. It usually means a shape change — a number serialised as a string, a body that was never parsed, or a field that became a list — and it points at the contract rather than at the data.
- How does the failure path differ for an XML payload?XML paths are rooted at `/` instead of `$` and are slash-separated, so a nested element reads `/a/b/c` and an attribute reads `/hello/@foo`. The block structure, the reason chain and the type pair are identical; only the path notation follows the document model.
- Why does `match a != b` give you no path information when it fails?Because `!=` inverts the equality check: it fails only when the two payloads *were* equal. There is no differing node to name, so the report can say nothing about where. Any question of the form "where do these two documents differ" has to be asked with `==`.
saying these in an interview costs you the question
- Expects a structural diff rather than a path trail
- Ignores the type pair and only reads the values
- Cannot say which end of the report names the leaf
- Thinks != also reports where the payloads differ
- Expects a length change to be reported as a run of per-index mismatches
- Assumes the whole document is reprinted at every level