A Karate `Scenario Outline` is driven by a single-cell `Examples:` table holding `read('users.csv')`, and the step `match __row.age == 30` fails. Why, and what are your options?
answer
- The reader returns text, not values
- There is no header left to hint
- The message cannot show quotation marks
- Fix it at the source, not per step
- Pin the row's shape once per outline
basics
~20 sKarate's CSV reader hands back every field as a string, so __row.age is '30', not 30, and a strict match against a number fails. CSV carries no types, and a dynamic table has no ! header to hint one.
solid answer
~50 sKarate reads a CSV into a list of maps in which **every field is a string** — the reader returns raw text and nothing converts it — so the row's `age` is `'30'`. `match` is strict about type, and the failure message shows `30` against `30` because it cannot draw quotation marks around a type. The `!` type hint that would fix this is a **column-header** feature of a hand-written `Examples:` table, and a dynamic single-cell table has no headers at all. Three ways out, in the order I would try them: move the fixture to **JSON**, where types are already in the file; **convert inside the feature**, either per step or by mapping the list before it becomes the table; or **assert against the string** and accept that the fixture is untyped. JSON is usually right.
code
gherkin · 12 linesFeature: converting an untyped fixture once
@setup
Scenario:
* def raw = [{ name: 'Alice', age: '30' }, { name: 'Bo', age: '41' }]
* def users = karate.map(raw, function(r){ r.age = parseInt(r.age); return r })
Scenario Outline: <name> arrives typed
* match __row == { name: '#string', age: '#number' }
Examples:
| karate.setup().users |go deeper
Recall that a CSV read gives you strings for every field, so a match against a number fails even when both sides look identical in the message.
Explain why the ! type hint cannot help: it is a column-header feature and a dynamic table has no header, so the types are whatever the cell's expression produced.
Fix it at the boundary. Prefer a JSON fixture; if the CSV is someone else's, convert once in an @setup scenario, and pin the row shape so the next drift fails loudly.
Decide who owns the fixture format. A CSV a domain expert maintains buys collaboration and costs a conversion layer, and that trade should be made deliberately, not discovered.
## Why the match fails Two independent facts meet here, and each one alone is harmless. 1. **A CSV has no type information.** Karate's reader walks the records and puts each field into the row map exactly as the parser handed it over — a `String`, always. A column of `30` and a column of `Alice` are indistinguishable to it. 2. **`match` is strict about type.** `'30' == 30` is false, and the failure message renders both sides without quotation marks, so it reads as `30` against `30`. That is the single most time-wasting failure shape in a data-driven Karate suite. The instinct is to reach for the `!` type hint. It does not apply. The hint lives on a **column header** of a literal `Examples:` table, and a dynamic table is a single cell holding an expression — there is no header row to mark. Nothing in a dynamic table types anything; the types are whatever the expression produced. ## What each source actually gives you | Cell | Row values | Types | |---|---|---| | `read('users.json')` | one array element | from the file: numbers stay numbers | | `read('users.csv')` | one CSV record | **all strings**, always | | `karate.setup().data` | one element of a variable built in steps | whatever the steps built | | a literal `Examples:` table | one row | strings, unless the header carries `!` | So the *same outline body* behaves differently depending only on which file the cell reads, and nothing in the feature says so. That is the design constraint worth naming in review. ## The options, in the order to try them 1. **Move the fixture to JSON.** A JSON array already carries the types, `read('users.json')` drops straight into the same cell, and no step changes. This is right whenever the file is yours. It also makes nested data possible — a CSV cannot express an object per row at all. 2. **Convert on the way in.** When the CSV is produced by someone else — an export, a spreadsheet a domain expert maintains — map it once, in the cell or in an `@setup` scenario, rather than in every step: ```gherkin @setup Scenario: * def raw = read('users.csv') * def users = karate.map(raw, function(r){ r.age = parseInt(r.age); return r }) Scenario Outline: check <name> * match __row.age == '#number' Examples: | karate.setup().users | ``` One conversion site, expressed once, and the outline body stays clean. 3. **Convert per use.** `match parseInt(__row.age) == 30`. Cheapest to write and the worst to live with: it spreads the knowledge that this column is text across every step that touches it, and the next column someone adds will be missed. 4. **Assert against the string.** `match __row.age == '30'`. Legitimate when the value really is an opaque token — an account number with leading zeros, a postcode, a version string — and actively wrong when it is a quantity you will later compare or sum. ## How to make the class of bug loud The fix for one column is easy; the fix for the *category* is a row-shape assertion. Put one step at the top of the outline that pins the shape of every row: ```gherkin * match __row == { name: '#string', age: '#number', active: '#boolean' } ``` Now the failure lands on the row, naming the field, before any business assertion runs. That converts a puzzling `30` versus `30` several steps in to a single obvious message at the point of entry, and it catches the same problem when someone adds a column, swaps the fixture for a CSV export, or drops a `!` from a static table. ## The judgement behind the fix - **Prefer the source that carries types.** Every conversion step is a place where the fixture and the assertion can drift apart. JSON removes the class of bug rather than handling it. - **Convert once, at the boundary.** If the CSV is not yours, the boundary is where it enters — the `@setup` scenario or the cell — not every assertion downstream. - **Do not silently loosen the assertion.** Changing `== 30` to `== '30'` to make a red run green hides a real question: is this field a number to this system or not? Answering that is the point of the failure. - **Watch for the same shape elsewhere.** A hand-written table missing a `!`, a CSV export someone swapped in for a JSON fixture, and a `@setup` scenario that stringified a value all produce the identical symptom. One row-shape assertion covers all three. The broader rule: in a dynamic Karate outline, the row's types are decided entirely by the expression in the cell, and nothing downstream of the cell will type them for you.
- Why can't you add a `!` type hint to a Karate dynamic `Examples:` table to fix this?The hint is part of a column header, and a dynamic table has no header row — it is one cell holding an expression. The rows come out of that expression already typed, or already not. Adding `!` anywhere in the cell just changes the expression text, and it will fail to evaluate rather than type anything.
- When is asserting against the string form the right answer rather than a cop-out?When the field is genuinely an opaque token rather than a quantity: an account number with meaningful leading zeros, a postcode, a version string, an external reference id. Converting those to numbers loses information. The test is whether anything in the system ever does arithmetic or ordering on the value — if not, text is the honest type.
- A Karate outline passed for months and starts failing after someone swapped its JSON fixture for a CSV export. What single change would have caught it at the boundary?A row-shape assertion as the outline's first step — `match __row == { name: '#string', age: '#number' }`. It fails on the row, naming the field whose type changed, instead of surfacing as a confusing equality failure inside a business assertion several steps later.
saying these in an interview costs you the question
- Assumes Karate infers numbers when it reads a CSV file
- Tries to add a ! type hint to a dynamic single-cell Examples table
- Loosens the assertion to the string form without asking whether it is a number
- Converts the field inside every step that touches it
- Reads the failure message as a genuine value mismatch rather than a type one
- Thinks the row-shape check belongs in a separate scenario rather than the outline