When using withData with a data class per case, how do you produce readable test names, and what are the naming overloads?
answer
- Default name = toString()
- Map keys -> names; values -> lambda
- nameFn = { it -> "..." } overload
- Data class destructuring in lambda via componentN
- Avoid duplicate/opaque names
basics
~20 sBy default each generated test is named from the value's toString(). With a data class you get the auto-generated toString, which is usually readable. If you want nicer names, give withData a Map (keys become names) or a function that builds the name from each value.
solid answer
~40 swithData names each generated test from the element's toString() by default. For a data class, Kotlin's auto-generated toString() (e.g. Case(a=1, b=2, expected=3)) is often readable enough. To control names you have overloads: pass a Map<String, T> so the map keys become the test names; or use the nameFn overload, withData(nameFn = { case -> "..." }, first, second, ...) or withData(nameFn, collection), which computes a name from each element. There are also overloads taking a first element plus varargs, an Iterable, or a Sequence. Good naming matters because withData generates one test per row and duplicate or opaque names hurt the report and can collide. A common pattern is a data class holding inputs and expected output, with a nameFn that summarizes the case.
code
kotlin · 9 lineswithData(
mapOf(
"empty" to "",
"single" to "a",
"long" to "abcde",
)
) { input ->
input.length shouldBe input.count { true }
}go deeper
Knows names come from toString() and a data class usually reads fine.
Uses Map and nameFn overloads and destructures data-class cases in the lambda.
Designs case data classes plus nameFn for readable, collision-free reports and explains componentN destructuring.
Standardizes a team pattern (case data class + nameFn) so data-driven test output is self-documenting across the codebase.
## Default naming `withData` derives each generated test's name from the element's **`toString()`**. For primitives that's the literal value; for a **data class**, Kotlin's compiler-generated `toString()` prints all properties (`Case(a=1, b=2, expected=3)`), which is usually descriptive. ## Overloads for explicit names Kotest offers several `withData` shapes: - **Varargs:** `withData(t1, t2, t3) { ... }` — names from `toString()`. - **Iterable/Sequence:** `withData(listOf(...)) { ... }` and `withData(sequenceOf(...)) { ... }`. - **Map:** `withData(mapOf("adds positives" to Case(1,2,3))) { ... }` — **map keys become test names**, values are passed to the lambda. - **nameFn:** `withData(nameFn = { c -> "${c.a}+${c.b}=${c.expected}" }, c1, c2) { ... }` (also a collection overload) — compute the name from each element. ## Recommended data-class pattern ```kotlin import io.kotest.core.spec.style.FunSpec import io.kotest.datatest.withData import io.kotest.matchers.shouldBe data class Case(val a: Int, val b: Int, val expected: Int) class AddTest : FunSpec({ context("addition") { withData( nameFn = { "${it.a} + ${it.b} == ${it.expected}" }, Case(1, 2, 3), Case(0, 0, 0), Case(-1, 1, 0), ) { (a, b, expected) -> (a + b) shouldBe expected } } }) ``` Note the lambda **destructures** the data class via `(a, b, expected)`, which works because `data class` generates `componentN()` operators. ## Why naming matters - `withData` creates **one test per row**; clear names make the report readable. - **Duplicate names** are problematic — if several elements share a `toString()`, distinguish them with a `nameFn` or a Map. - Opaque defaults (e.g. a class without a custom `toString`) yield names like `Case@1a2b3c`; override `toString()` or use `nameFn`. ## Key takeaway Prefer a small **data class per case** plus a **`nameFn`** (or `Map`) so every generated test reads like a sentence.
- Why might a plain class (not data class) give bad test names?Without a custom toString(), its default is the identity hash (Foo@1a2b3c), which is opaque. Use a data class, override toString(), or supply a nameFn/Map.
- How does the lambda destructure a data class case?data class generates componentN() operators, so withData(...) { (a, b, expected) -> } destructures the element into named locals.
saying these in an interview costs you the question
- Believing you cannot customize generated test names
- Using a plain class with no toString and getting unreadable names
- Ignoring duplicate-name collisions across rows
- Thinking Map values become names (keys do)
- Not knowing destructuring relies on componentN() from data class