Where can Kotest's `withData` be placed inside a spec — at the spec root, inside a container, or inside another `withData` — and what kind of node does each element become?
answer
- root, inside container, inside another withData
- body is a ContainerScope → nesting is legal
- node resolves leaf-or-container by what the body registers
- nesting = cross-product of leaves, names compose down the tree
- sequence is consumed at registration — bound it
basics
~20 sIt can sit at the spec root, inside a container such as FunSpec's context, and inside another withData. Each element is registered as a node whose body is a container scope: it reports as a leaf test if nothing nested is registered, and as a container if the body registers more tests.
solid answer
~50 s`withData` is available wherever a container may be registered — the root scope of a nesting spec style, inside a `context`/`describe` block, and inside the body of another `withData`. That is why nesting works at all: the lambda `withData` hands you is a **container scope**, not a plain test scope, so it may itself register tests. Each element becomes one node. In Kotest 5 that node is dynamic: if its body only asserts, it resolves to a leaf test; if the body registers further tests (a nested `withData`, or an explicit `test(...)`), it resolves to a container with those children beneath it. So an outer `withData` over configurations and an inner `withData` over inputs produces a tree of `config × input` leaves, each independently named, reported and re-runnable. The requirement is a style with nesting scopes; a flat style with no container concept has nowhere to hang the children.
code
kotlin · 19 linesclass PositionsTest : FunSpec({
// 1. spec root
withData(1, 2, 3) { n -> n shouldBeGreaterThan 0 }
// 2. inside a container
context("free shipping threshold") {
withData(carts) { cart -> shipping(cart) shouldBe 0 }
}
// 3. nested: outer element becomes a container, inner elements are the leaves
context("parser matrix") {
withData(listOf(Locale.US, Locale.GERMANY)) { locale ->
withData(listOf("1.5", "1,5")) { text ->
parse(text, locale) shouldNotBe null
}
}
}
})go deeper
Know that withData creates one test per element and can be wrapped in a context to give the group a name.
Explain that the body is a container scope, so withData nests and produces a cross-product of leaves.
Add the dynamic leaf-or-container resolution, per-case lifecycle callbacks and filtering, and the cost of large matrices.
Treat it as report-tree design: decide which dimensions genuinely interact, cap cross-products, and note that a flat-style house convention rules out nested data tests.
## The rule in one sentence `withData` may be used anywhere Kotest allows a container to be registered, and the lambda it gives you is itself a container scope — which is precisely what makes nesting legal. ## The three positions **1. Spec root.** In a nesting style such as `FunSpec`, `DescribeSpec` or `ShouldSpec`, you can call `withData` directly in the spec's initializer, producing top-level nodes with no wrapping container. Convenient, but the report loses the sentence that says *what* is being parameterised, so most teams wrap it. **2. Inside a container.** The common shape: ```kotlin context("shipping is free over the threshold") { withData(cases) { case -> ... } } ``` The container names the behaviour under test; the generated names identify the case. This reads well in a CI report: one heading, N cases. **3. Inside another `withData`.** The outer element's body is a container scope, so it can register a second `withData`: ```kotlin withData(configs) { config -> withData(inputs) { input -> assertSomething(config, input) } } ``` This is a cross-product: one leaf per `(config, input)` pair, each with a name composed from the two levels of the tree. It is the idiomatic Kotest answer to "run this matrix of cases". ## What each element is registered as The subtle part. Kotest does not decide up front whether an element is a leaf or a container — in Kotest 5 the node is **dynamic**: its type is resolved by what happens when its body executes. Register nothing inside and it is a leaf test that passes or fails on its own assertions. Register children inside (a nested `withData`, or an explicit `test("...")`) and it becomes a container whose result is derived from its children. Two consequences fall out of this: - **A container's own assertions are not a good place to put logic.** Mixing assertions with nested registration in the same body is confusing to read and to report; keep a level either branching or asserting. - **The body is a suspending container scope**, so a data test body can call suspend functions directly, exactly as an ordinary Kotest test body can. ## Style prerequisites Nesting requires a spec style that *has* container scopes. In a style whose root only admits leaf tests, there is no scope for children to attach to, so a nested `withData` has nowhere to go — if you want matrices, pick a nesting style (`FunSpec` with `context`, `DescribeSpec` with `describe`, `ShouldSpec` with `context`). This is a real constraint when a team has standardised on a flat style and then wants parameterised matrices. ## Practical consequences of "one node per element" Because every element is a real node rather than a loop iteration: - **Failures are isolated per case.** One failing input does not prevent the others from running or reporting. - **Lifecycle callbacks fire per node.** `beforeTest`/`afterTest` see each generated case, so per-case setup and teardown happen the way they would for hand-written tests. (Whether the *spec instance* is recreated is a separate matter governed by the isolation mode.) - **Each case is individually addressable** by the IDE and by Kotest's name-based test filtering — which only works if the generated names are stable, so the naming machinery and the registration machinery are two halves of the same feature. - **Registration order is the collection's order.** `withData` over a `List` reports in list order; over a `Sequence` it consumes the sequence at registration time, so an infinite or expensive sequence is a trap — bound it first. ## Cost of a large cross-product A nested `withData` multiplies. Two levels of 50 elements is 2,500 leaves, each with a name computed at registration and a node kept in the report tree. That is usually fine, but it shows up as slow IDE trees and enormous XML reports, and it is a signal that you may want property-based testing over an enumerated matrix instead. Keep matrices to the dimensions that genuinely interact. ## How to say it in an interview "`withData` goes anywhere a container can be registered: spec root, inside a `context`, or inside another `withData`. Each element becomes a node whose body is a container scope, so it resolves to a leaf if it only asserts and to a container if it registers children — which is how nesting produces a cross-product of independently named, independently reported leaves. It needs a nesting spec style, and the cross-product multiplies, so bound the dimensions."
- How does Kotest decide whether a `withData` element shows up as a test or as a container in the report?It is decided by what the element's body registers. The node is dynamic: run the body, and if nothing further was registered it is reported as a leaf test whose result comes from its own assertions; if the body registered children — a nested `withData` or an explicit test — it becomes a container and its result is derived from those children. You never declare this yourself.
- What breaks if you nest `withData` in a flat spec style?A flat style has no container scope for children to attach to, so there is nowhere to register the inner cases. Matrices need a nesting style — `FunSpec` with `context`, `DescribeSpec` with `describe`, `ShouldSpec` with `context`. If a codebase has standardised on a flat style, that decision quietly rules out nested data tests, which is worth raising when the convention is set rather than discovering it mid-feature.
saying these in an interview costs you the question
- "withData can only be used inside a context block" — the spec root of a nesting style works too.
- "Nested withData isn't supported" — it is; the body is a container scope precisely so it can register more tests.
- "The outer withData element is always a container" — it is a dynamic node; with no nested registration it is reported as a leaf test.
- "Nesting runs each pair inside one shared test" — each pair is its own leaf, independently named, reported and re-runnable.
- "You can feed it an infinite sequence and it will lazily generate tests forever" — registration consumes the sequence up front, so bound it.