skip to content

What is Kotest's withData and why would you use it instead of writing a separate test for each input?

level: juniorimportance: must knowfreq 55%

answer

  1. io.kotest.datatest.withData
  2. One body, many rows, separate tests
  3. Name from toString() by default
  4. Lives inside context/container
  5. Beats forEach: per-case reporting

basics

~10 s

withData runs the same test body once for each value you give it. Instead of copy-pasting a test many times, you list the inputs and Kotest creates a separate test for each one automatically.

solid answer

~40 s

withData is Kotest's data-driven testing helper (from the io.kotest.datatest package). You pass it a collection of inputs and a lambda; Kotest invokes that lambda once per input, generating a distinct, individually-reported test for each row. This avoids duplicating assertions and gives you per-case pass/fail results so you immediately see which input broke. It works inside container scopes like context (in FunSpec/BehaviorSpec) or as a top-level test in spec styles that support it. Test names are derived automatically from each element's toString(), or you can supply explicit names via a Map or a nameFn. Compared to a loop with forEach inside one test, withData reports each case separately and keeps running the rest after one fails.

code

kotlin · 7 lines
kotlin
class IsEvenTest : FunSpec({
    context("isEven") {
        withData(2, 4, 6) { n ->
            (n % 2 == 0) shouldBe true
        }
    }
})

go deeper

for a junior

Knows withData runs one body over many inputs and creates a test per input.

for a middle

Explains per-case reporting, default naming via toString(), and placement inside container scopes.

for a senior

Contrasts with forEach loops and JUnit parameterized tests; controls naming and chooses appropriate overloads.

for a principal

Frames data-driven tests within a team testing strategy: when table tests aid readability vs. when property-based tests are stronger.

## What withData is `withData` is Kotest's primary **data-driven (table-driven) testing** tool. It lives in the `io.kotest.datatest` package and lets you run **one test body across many input rows**, with Kotest generating a **separate, independently-reported test** for each row. ## Why not just a loop If you write `listOf(1,2,3).forEach { assert(...) }` inside a single test, all cases share one test result. The first failure aborts the rest, and the report shows one test, not which input failed. `withData` instead creates N tests, so: - Each case has its **own name** in the report. - A failure in case 2 does not stop cases 3..N. - You see exactly **which input** failed. ## Basic usage ```kotlin import io.kotest.core.spec.style.FunSpec import io.kotest.datatest.withData import io.kotest.matchers.shouldBe class SquareTest : FunSpec({ context("square of n") { withData(1, 2, 3) { n -> (n * n) shouldBe n * n } } }) ``` Here `withData(1, 2, 3)` runs the lambda three times. Kotest derives each test name from the element's `toString()` (e.g. "1", "2", "3"). ## Where it can be called - Inside a **container scope** such as `context("...") { ... }` in `FunSpec`, or `Given/When` in `BehaviorSpec`. - As a top-level data test in styles that allow it. - It is **not** a matcher; it is a test-generating construct, so it belongs where tests/containers are declared, not inside an assertion. ## Naming - Default: each element's `toString()` becomes the test name. - Custom: pass a `Map<String, T>` (keys are names) or use the `nameFn` overload `withData(nameFn = { "case $it" }, data) { ... }`. ## Key takeaway Reach for `withData` whenever you have **the same assertions over many inputs** and want clear per-case reporting.

  • How are the test names generated by default?
    From each data element's toString(). For readable names with custom types, override toString(), pass a Map of name->value, or use the nameFn overload.
  • What import do you need?
    import io.kotest.datatest.withData (the data-driven helpers are in the kotest-framework-datatest module / io.kotest.datatest package).

Like a stamp: you define the shape once and press it onto each piece of paper (input) to get an individual result.

saying these in an interview costs you the question

  • Claiming withData is a matcher or assertion
  • Saying it is identical to a forEach loop (loses per-case reporting and continues-on-failure)
  • Thinking it stops after the first failing row
  • Not knowing the io.kotest.datatest package
  • Believing it requires JUnit @ParameterizedTest

context