skip to content

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

level: seniorimportance: should knowfreq 32%

answer

  1. Lambda is suspend -> call suspend funs directly
  2. Nest withData for cartesian matrix
  3. Each row is a real test (beforeTest/afterTest per row)
  4. Watch test-count explosion
  5. No runBlocking needed

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.

solid answer

~40 s

withData's block is a suspending lambda running in Kotest's coroutine-based test scope, so you can call suspend functions (e.g. a suspending service, runBlocking is unnecessary) directly inside it. withData can also be nested: calling withData again inside a withData block (or inside a context container) produces a cartesian-style tree of generated tests, each leaf an independent test. This is handy for matrix testing (e.g., every browser x every locale). Because each row is its own test, Kotest lifecycle callbacks (beforeTest/afterTest) and per-test config apply per row. You can attach config via .config or the config-aware overloads (timeouts, tags) where supported. Keep nesting shallow for readability; deep cartesian products explode test counts. The suspend support is why withData pairs cleanly with coroutine code and Kotest's runTest-style flows.

code

kotlin · 7 lines
kotlin
context("grid") {
    withData(1, 2) { x ->
        withData(10, 20) { y ->
            (x + y) shouldBe x + y // 4 leaf tests: (1,10)(1,20)(2,10)(2,20)
        }
    }
}

go deeper

for a junior

Knows the lambda can call suspend functions and that nesting is possible.

for a middle

Builds a nested matrix and understands each leaf is its own test.

for a senior

Explains suspend-scope mechanics, per-row lifecycle/config, and manages combinatorial explosion deliberately.

for a principal

Weighs matrix testing cost vs. value, sets guardrails on nesting depth, and integrates coroutine test strategy (runTest/virtual time) across the suite.

## Coroutine (suspend) support Kotest test bodies run inside a **coroutine scope**, and `withData`'s lambda is a **`suspend` lambda**. That means you can **call suspend functions directly** — no `runBlocking` wrapper: ```kotlin import io.kotest.core.spec.style.FunSpec import io.kotest.datatest.withData import io.kotest.matchers.shouldBe class FetchTest : FunSpec({ context("fetch user") { withData(1L, 2L, 3L) { id -> val user = userService.fetch(id) // suspend fun user.id shouldBe id } } }) ``` The coroutine context comes from Kotest's framework; for testing flows/virtual time you typically combine your own `runTest` inside the body or use Kotest extensions. ## Nesting withData You can **nest** `withData` to build a **matrix** of cases. Each inner row becomes a leaf test under its outer parent: ```kotlin context("matrix") { withData("chrome", "firefox") { browser -> withData("en", "de") { locale -> render(browser, locale) shouldNotBe null } } } ``` This yields a cartesian product: chrome/en, chrome/de, firefox/en, firefox/de — **four leaf tests**, each independently reported. ## Lifecycle and config per row Because **each generated row is a real Kotest test**: - `beforeTest`/`afterTest` callbacks fire **per row**. - You can attach per-test config (timeouts, tags, retries) via the `.config { ... }` block or config-aware overloads where the spec style supports it. - Failures are isolated to their leaf. ## Cautions - **Test-count explosion:** nested cartesian products multiply; `3 x 4 x 5` = 60 tests. Keep nesting shallow and intentional. - **Readability:** deeply nested `withData` can obscure intent; sometimes a single data class with all fields plus one `withData` is clearer. - **Shared mutable state** across rows is risky because rows are independent tests; prefer per-row setup. ## Key takeaway `withData` is **coroutine-friendly** (suspend lambda) and **nestable** (matrix grids), with each row a first-class test honoring lifecycle and config — but watch the combinatorial blow-up.

  • Do you need runBlocking inside a withData lambda to call a suspend function?
    No. The withData lambda is a suspend block in Kotest's coroutine scope, so you can invoke suspend functions directly.
  • What is the main downside of deeply nested withData?
    Combinatorial test-count explosion and reduced readability; a cartesian product multiplies quickly, so keep nesting shallow or use a single case data class.

Nested withData is like nested for-loops that each emit a labeled test instead of just running code.

saying these in an interview costs you the question

  • Wrapping the body in runBlocking unnecessarily
  • Believing withData cannot be nested
  • Thinking lifecycle callbacks fire once for the whole block, not per row
  • Ignoring combinatorial explosion in nested matrices
  • Sharing mutable state across rows assuming sequential single-test semantics

context