skip to content

Data-Driven Tests

Table-driven tests run the same body over many rows of parameters, with names generated per row so failures point at the right case. It is the cleanest answer to a suite full of near-identical copy-pasted tests.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

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

open as a page

Explain how Kotest's table(), row(), and forAll work together for table-driven testing. Show a multi-column example.

level: middleimportance: must knowfreq 50%

basics

~10 s

You build a table from rows, where each row holds several values (the inputs and expected output). Then forAll runs your check once per row, passing that row's values into your lambda.

open as a page

When using withData with a data class per case, how do you produce readable test names, and what are the naming overloads?

level: middleimportance: should knowfreq 40%

basics

~20 s

By 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.

open as a page

Can withData be nested, and can its lambda call suspend functions? Explain how nesting and coroutine support work.

level: seniorimportance: should knowfreq 32%

basics

~20 s

Yes to both. You can put a withData inside another withData (or inside a context) to build a grid of cases, and the test lambda is a suspend block, so you can directly call suspending functions without extra wrappers.

open as a page

As a tech lead, how do you decide between withData/table-driven tests and property-based forAll, and what are the trade-offs of each for a given test surface?

level: principalimportance: should knowfreq 24%

basics

~20 s

Use table-driven tests (withData/table) when you have specific, known examples and exact expected outputs. Use property-based forAll when you want many random inputs to check a general rule. Tables document edge cases; properties find unknown ones.

open as a page