Two elements passed to Kotest's `withData` produce the same generated test name. What does Kotest do, how do you change that behaviour, and why does it matter operationally?
answer
- DuplicateTestNameMode: Warn (default) / Silent / Error
- AbstractProjectConfig.duplicateTestNameMode
- index suffixes are positional — shift when the list changes
- unique per scope, not globally
- root cause is the name derivation, not the mode
basics
~20 sKotest requires unique names within a scope, so its DuplicateTestNameMode decides: the 5.x default Warn logs a warning and appends an index to disambiguate. Set duplicateTestNameMode = DuplicateTestNameMode.Error in AbstractProjectConfig to fail instead. Duplicates break re-runs, filtering and CI history.
solid answer
~50 sTest names are identifiers inside their scope, so a collision has to be resolved somehow. Kotest's `DuplicateTestNameMode` governs it: `Warn` (the Kotest 5.x default) logs a warning and disambiguates by appending an index; `Silent` disambiguates without complaining; `Error` fails, turning the collision into a build failure. You set it globally on `AbstractProjectConfig.duplicateTestNameMode`. Collisions in `withData` almost always mean the naming machinery fell back — the inputs are not data classes, don't implement `WithDataTestName` and aren't annotated `@IsStableType`, so every element got the same type-derived name. A `nameFn` that ignores a distinguishing field does the same. Operationally it matters because names are what the IDE re-runs, what name-based filters match, and what CI history and flaky-test dashboards key on. Index-suffixed names also shift when you reorder or insert a case, so yesterday's `Foo (3)` is today's `Foo (4)`. On a large data-driven suite, `Error` is the setting that keeps the report honest.
code
kotlin · 6 linesimport io.kotest.core.config.AbstractProjectConfig
import io.kotest.core.names.DuplicateTestNameMode
object ProjectConfig : AbstractProjectConfig() {
override val duplicateTestNameMode = DuplicateTestNameMode.Error
}go deeper
Know that Kotest test names must be unique in their scope and that duplicates usually mean the input type isn't naming itself well.
Name DuplicateTestNameMode, its three values and the Warn default, and identify the fallback naming as the usual root cause.
Argue the operational case: index suffixes are positional, so re-runs, name-based filtering and CI history break; set Error and fix the derivation upstream.
Make it policy — Error in project config plus a convention that data-test inputs name themselves — so reporting quality is enforced by the build rather than by review.
## Why duplicates arise at all `withData` (package `io.kotest.datatest`) registers one node per element and derives each name from the value. If the type does not implement `WithDataTestName`, is not annotated `@IsStableType`, and is not a data class, Kotest falls back to a name based on the value's **type** rather than its contents — so all N elements ask for the same name. The other common source is a `nameFn` that projects away the field that actually distinguishes the cases (`{ "case for ${it.userId}" }` when three cases share a user id), and the map overload cannot collide at Kotest level at all because duplicate keys already collapsed inside the map literal before Kotest saw them. ## What Kotest does A test name has to be unique within its scope, because it is the address of that node. Kotest 5 exposes the policy as `DuplicateTestNameMode`: - **`Warn`** — the default. Logs a warning and disambiguates the name, typically by appending an index. The run stays green. - **`Silent`** — disambiguates with no warning. Only sensible when you have a deliberate reason to tolerate collisions. - **`Error`** — the collision fails, so the problem surfaces as a red build rather than an unreadable report. You set it project-wide in your `AbstractProjectConfig`: ```kotlin object ProjectConfig : AbstractProjectConfig() { override val duplicateTestNameMode = DuplicateTestNameMode.Error } ``` ## Why silent disambiguation is worse than it looks The default keeps the build green, which is exactly why the defect survives review. Three concrete costs: **1. You cannot tell which case failed.** The report says `Input (7)` failed. Nothing about `Input (7)` tells you which of your inputs it was; you have to count elements in the source list and hope the order is what you think it is. **2. Index-suffixed names are positional, not semantic.** Insert a case at the front of the list and every subsequent suffix shifts by one. Yesterday's `Input (3)` is today's `Input (4)`. Anything that remembers names across runs — CI test history, flaky-test tracking, quarantine lists, a saved IDE run configuration — is now pointing at a different case, silently. **3. Name-based selection stops working.** Kotest can filter tests by name/path. If ten cases share a base name, you cannot address one of them; you either run all ten or none, which is precisely the capability data tests were supposed to give you over a hand-written loop. ## The fix is upstream, not the mode Setting `Error` does not make names good; it makes bad names loud. The actual fix is the naming machinery: - make the input a **data class** so its `toString()` names it; - implement **`WithDataTestName`** and return something that identifies the case; - annotate a class with a good hand-written `toString()` as **`@IsStableType`**; - pass a **`nameFn`** that includes every field that varies across the case list; - or use the **map overload** and write the names yourself — remembering that duplicate map keys drop a case entirely before Kotest is involved, which is a worse failure than a collision because nothing warns at all. A useful review heuristic: if two cases in a list would produce the same sentence when read aloud, either they are genuinely the same case (delete one) or the name is missing the dimension that distinguishes them. ## Scope of uniqueness Uniqueness is required **within a scope**, not globally. Two different `context` blocks may each contain a case named `"empty input"` without any collision, because their full paths differ. This is another argument for wrapping `withData` in a named container: it both documents the behaviour under test and widens the space of acceptable case names. ## Interaction with nested `withData` In a nested (cross-product) data test, each level names its own node and the full path is the composition. Collisions therefore only need to be resolved among the siblings at one level. A cross-product with a poorly-named outer level produces `Config`, `Config (1)`, `Config (2)` as the containers, and every child underneath inherits that ambiguity in its path — so fix the outer level first; it multiplies. ## How to say it in an interview "Kotest needs unique names per scope, so `DuplicateTestNameMode` decides — the 5.x default `Warn` appends an index and keeps the build green, `Error` fails it. Duplicates in `withData` mean the stable-identifier logic fell back to a type name, or a `nameFn` dropped the distinguishing field. I'd set `Error` in project config on a data-heavy suite, because index-suffixed names are positional: they shift when the list changes and break re-runs, filtering, and CI history."
- Why is `DuplicateTestNameMode.Error` often the right project-wide setting for a data-heavy suite?Because the default `Warn` keeps the build green while producing a report you cannot act on — a failing `Input (7)` does not tell you which input failed, and the index shifts whenever the case list changes. `Error` converts a silent reporting defect into a build failure at the moment someone introduces it, when the fix (a data class, a better `nameFn`) is cheap and obvious.
- Do two tests in different `context` blocks with the same name collide?No — uniqueness is required within a scope, and the full path of each node includes its containers. Two sibling containers may each hold a case called "empty input" without any conflict. This is a practical reason to wrap `withData` in a named container: it documents what is being parameterised and gives short case names more room to be distinct.
saying these in an interview costs you the question
- "Duplicate test names fail the build by default" — the Kotest 5.x default is Warn, which disambiguates and stays green.
- "Names have to be globally unique across the project" — uniqueness is per scope; the container path disambiguates.
- "Setting DuplicateTestNameMode.Error fixes the names" — it only makes bad names loud; the fix is in the name derivation.
- "The (1), (2) suffixes are stable identifiers I can filter on" — they are positional and shift when the case list changes.
- "The map overload just warns on duplicate keys" — the duplicate collapses inside the map before Kotest sees it, so a case silently disappears.