skip to content

In a Karate feature file, what turns an `Examples:` table into a dynamic one — so its rows come from `read('cats.json')` or `karate.setup()` instead of being typed out — and what must that cell evaluate to?

level: middleimportance: must knowfreq 60%

answer

  1. The table's shape decides, not a tag
  2. One column, and nothing under the header
  3. That cell is an expression, not a name
  4. Must yield a list or a generator function

basics

~20 s

Karate treats an Examples table as dynamic only when it is a single cell: one column and one row, the header, with nothing under it. That cell is evaluated as JavaScript and must return a list of objects or a generator function.

solid answer

~40 s

A Karate `Examples:` table becomes **dynamic** when it holds exactly one cell — one column, and no data rows under the header. Karate reads that lone cell as a *scenario expression*, evaluates it before the outline's scenarios exist, and spawns one scenario per item it yields. The expression must return either a **list of objects** — `read('cats.json')`, `read('users.csv')`, `karate.setup().kittens`, a literal `[{ a: 1 }, { a: 2 }]` — or a **generator function** that Karate calls with the row index and that returns `null` to stop. The row count is therefore decided at run time rather than at parse time. Add a second column or a second row and the table quietly reverts to an ordinary static outline.

code

gherkin · 12 lines
gherkin
Feature: dynamic rows

@setup
Scenario:
* def kittens = [{ name: 'Bob', age: 5 }, { name: 'Nyan', age: 7 }]

Scenario Outline: create <name>
* print 'creating', name, 'aged', age
* match __row == { name: '#string', age: '#number' }

Examples:
| karate.setup().kittens |

go deeper

for a junior

Recall the shape: one column, one row, nothing underneath. That single cell is a Karate expression such as read('cats.json'), and each object it returns becomes one scenario.

for a middle

Explain that Karate evaluates the cell in its own runtime before any scenario exists, accepts a list of objects or a generator function called with the row index, and rejects anything else.

for a senior

The judgement is in the failure modes: a one-column table you forgot to fill is silently dynamic, a header-only two-column table runs nothing, and the row count exists only at run time.

for a principal

Weigh a run-time row count against everything downstream that wants one up front — shard planners, flake budgets, per-case history — and decide where the data source is allowed to live.

## The trigger is the shape of the table, not a keyword Karate has no `@dynamic` tag and no special `Examples:` syntax for this. The switch is structural. When the parser builds an `Examples:` table it looks at two numbers and only two: the column count and the row count. If the table has **exactly one column and exactly one row** — a header line with nothing beneath it — Karate keeps the text of that lone cell as the outline's *dynamic expression* and discards the table. Every other shape is an ordinary row-by-row table. ```gherkin Scenario Outline: create <name> * print 'creating', name Examples: | read('cats.json') | ``` That cell is read as a Karate expression, never as a column name. There is no header to name, so there is nothing to write `<...>` against until the expression has produced a row. ## What the expression must return Karate evaluates the cell in a throwaway runtime of its own, before any of the outline's scenarios exist, and accepts exactly two results: 1. **A list of objects.** Each element must be a map, and each map becomes one scenario's row data. An element that is not a map does not produce a scenario. 2. **A generator function.** Karate calls it repeatedly with the row index — `0`, `1`, `2`, … — and keeps going until a call returns `null`, or anything that is not an object. This is the option for data you do not want to materialise: only one row exists at a time. Anything else — a bare number, a string, a single object — is rejected, and the outline fails with an error naming the expression that produced it. ## Where the rows normally come from | Source cell | What each row is | Types | |---|---|---| | `read('cats.json')` | one element of the JSON array | preserved from the file | | `read('users.csv')` | one CSV record | **every field a string** | | `karate.setup().data` | one element of a variable built by an `@setup` scenario | preserved | | `karate.mapWithKey(karate.range(1, 50), 'i')` | `{ i: 1 }`, `{ i: 2 }`, … | preserved | | `[{ a: 1 }, { a: 2 }]` | a literal element | preserved | The CSV row is the one people get wrong. A CSV carries no type information, so `age` arrives as `'30'` rather than `30`. The `!` type hint that fixes that on a hand-written table is a *column header* feature, and a dynamic table has no headers to hint. ## `karate.setup()` and the `@setup` scenario `karate.setup()` is the built-in way to build rows with real Karate steps instead of cramming everything into one expression: - Tag a `Scenario` in the same feature with `@setup`. A scenario carrying that tag is **excluded from normal selection** — it runs only when `karate.setup()` asks for it, and it never appears as a result of its own. - `karate.setup()` runs it and returns **all** of its variables as one object, so `karate.setup().kittens` pulls out the array you built inside it. - It runs with the **`Background` skipped**, so it cannot depend on anything the `Background` defines. The `Background` still runs for each spawned row scenario, as usual. - `karate.setup('name')` picks one when a feature carries several, matched against `@setup=name`. This is what lets the rows come from a live source — an HTTP call that lists what actually exists, a query through Java interop — rather than a checked-in fixture. ## The quiet failure modes - **A one-column table with no data rows is dynamic whether you meant it or not.** Write `| name |` and forget the rows, and Karate evaluates `name` as an expression instead of telling you the table is empty. - **A two-column header with no data rows produces zero scenarios and no complaint.** The row loop starts after the header and there is nothing after it. A green run with a suspiciously low scenario count is the only symptom. - **The `Background` has not run when the cell is evaluated**, so a variable defined there is not in scope for the expression. - **The row count does not exist until run time.** Anything that wants it up front — a shard planner, a fixed-size report, a per-case dashboard keyed on a known set — has to cope with a number that cannot be read out of the file. ## Why it earns its place Cucumber's `Examples:` is fixed at parse time: the rows in the file are the rows you get. Karate's single-cell form removes that ceiling without adding syntax. The same outline body runs against three rows in a smoke profile and three thousand in a nightly, purely by swapping what the cell reads, and the generator form does it without ever holding the set in memory. Paired with the column variables Karate binds automatically, it is the reason a Karate suite rarely grows a separate data-provider layer at all.

  • What does a generator function in a Karate dynamic `Examples:` cell receive, and how does it signal that it is finished?
    Karate calls it once per row with the zero-based row index as its argument, so the function can decide when to stop from the index alone. Returning `null` — or anything that is not an object — ends the outline, and the rows produced so far are the scenarios that run. Only one row is ever held at a time, which is why it suits data sets too large to load.
  • Why does a Karate `Scenario` tagged `@setup` not appear as its own result in a normal run?
    The tag removes it from ordinary selection, so nothing runs it unless `karate.setup()` asks for it; its steps are then attached to the report under the scenario that called it. It also executes with the `Background` skipped, so it cannot read anything the `Background` defines.
  • How would you drive a Karate outline over a numeric sequence without writing a fixture file at all?
    Put an expression that builds the list straight into the cell. `karate.mapWithKey(karate.range(1, 50), 'i')` turns the numbers 1..50 into `[{ i: 1 }, { i: 2 }, …]`, which is exactly the list-of-objects shape a dynamic table wants, and each scenario then has `i` bound as a variable.

A static Examples table is a printed guest list. The single-cell form is a pointer to the door: the same scenario runs, but how many times is only known once someone opens it.

saying these in an interview costs you the question

  • Says a special tag or keyword switches an Examples table to dynamic mode
  • Thinks any one-column Examples table is dynamic, even with data rows under it
  • Expects the cell to return a single object rather than a list of them
  • Assumes Background variables are in scope when the cell is evaluated
  • Believes the row count can be read from the feature file before the run
  • Thinks an @setup scenario also runs on its own as a normal scenario