Kotest's table DSL offers `forNone` alongside `forAll`. What does `forNone` actually assert about each row, and how do candidates typically misuse it?
answer
- forNone: the block must THROW for every row
- a row completing normally = the failure
- both runners keyed on throwing, never on a returned Boolean
- any throwable counts — even an NPE from broken setup
- need a specific exception? shouldThrow<T> inside forAll
basics
~20 sforNone inverts the pass condition: the block must FAIL for every row. A row that runs the block without throwing is what makes the test fail. It is not a predicate — a block returning false asserts nothing, so a boolean body makes forNone pass or fail for the wrong reason.
solid answer
~50 sBoth `forAll` and `forNone` in Kotest's `io.kotest.data` table DSL run an **assertion block** per row; they differ only in what counts as success. `forAll` requires the block to complete without throwing for every row. `forNone` requires it to throw for every row — the test fails if any row's block completes normally, and the error identifies that row by its header/value pairs. So `forNone` says "none of these rows satisfies this assertion", which is genuinely useful for negative tables: a set of inputs none of which should be accepted, none of which should match, none of which should be classified a certain way. The misuse is treating it as a predicate: writing `forNone(t) { a, b -> a + b == 0 }` returns a `Boolean` that Kotest never inspects. The block never throws, so `forNone` sees every row "passing the assertion" and fails — or, symmetrically with `forAll`, it never throws and the test is permanently green. Always assert inside the block.
code
kotlin · 15 linesimport io.kotest.data.forNone
import io.kotest.data.headers
import io.kotest.data.row
import io.kotest.data.table
val malformed = table(
headers("input"),
row(""),
row("no-at-sign"),
row("@nolocalpart"),
)
test("no malformed address is accepted") {
forNone(malformed) { input -> input shouldMatch emailRegex }
}go deeper
Know that forNone means the assertion must fail for every row, and that you still write normal matchers inside the block.
State the inverted success condition precisely — block must throw for every row — and identify the boolean-predicate misuse.
Add that any throwable satisfies forNone, so it can pass for the wrong reason, and prefer shouldThrow<T> inside forAll when the failure identity matters.
Set the convention: negative tables state the specific expected failure rather than 'something went wrong', and code review requires a matcher call in every table-runner body.
## The two table runners Kotest's table DSL (`io.kotest.data`) gives you two ways to execute a table: ```kotlin forAll(table) { a, b -> /* must NOT throw for any row */ } forNone(table) { a, b -> /* must throw for EVERY row */ } ``` Both iterate every row and hand its columns to the block as separate typed parameters. The only difference is the success condition, and both are defined in terms of **whether the block throws**, not what it returns. - `forAll` passes when no row's block throws. If some do, it aggregates the failures and reports each failing row with its header/value pairs. - `forNone` passes when *every* row's block throws. If some row completes normally, that is the defect, and the error identifies that row. ## Why "must throw" is the contract Kotest's assertions signal failure by throwing (`shouldBe` throws an `AssertionError` on mismatch). Defining the runners in terms of thrown errors is what lets you write the same natural assertion in both: ```kotlin // every row is a valid email forAll(valid) { input -> input shouldMatch emailRegex } // no row is a valid email forNone(invalid) { input -> input shouldMatch emailRegex } ``` The second reads as "none of these satisfies `shouldMatch emailRegex`", which is exactly the negative case you want, and you did not have to invert the matcher or write `shouldNotMatch` row by row. ## Where `forNone` genuinely earns its place - **Rejection tables.** A list of malformed inputs, none of which should parse into a valid value. - **Guarding an invariant across a set.** A set of states from which a transition must not be available. - **Anti-regression sets.** Inputs that used to be wrongly accepted; `forNone` documents that none of them is accepted any more. The expressive advantage over `forAll` with a negated matcher is small but real: `forNone` keeps the *positive* statement of the property in the block, so the table reads as "here are the cases where this property must not hold". ## The misuse: predicates instead of assertions The single most common mistake is writing a boolean expression: ```kotlin forNone(t) { a, b -> a + b == 0 } // WRONG ``` Kotlin happily compiles this — the lambda returns `Boolean` and Kotest ignores the return value. Since the body never throws, no row "fails the assertion", so from `forNone`'s point of view every row satisfied the block, and the check fails regardless of your data. The mirror-image bug with `forAll` is worse because it is silent: `forAll(t) { a, b -> a + b == 0 }` never throws, so the test is permanently green no matter how broken the code is. A useful review rule: **the body of a table runner must contain a matcher call** (`shouldBe`, `shouldThrow`, `shouldMatch`, …). If the last expression is a comparison, it is a bug. This confusion is fed by a naming coincidence: `forAll` also exists in Kotest's property-testing module, and *that* one has a boolean-predicate flavour. They are different functions in different packages with different contracts. If a table test suddenly stops compiling, or behaves as if it is not asserting anything, check whether the wrong `forAll` was imported. ## Two subtleties worth knowing **`forNone` swallows all exception types.** A row "fails the assertion" whenever its block throws — including a `NullPointerException` from a bug in your test setup, not just an `AssertionError` from a matcher. That means a `forNone` table can pass for entirely the wrong reason: your code under test blew up on every row, and `forNone` reads that as "none satisfied the assertion". If the negative case you care about is a specific exception, assert it explicitly with `shouldThrow<T>` inside a `forAll` instead, which pins down *which* failure occurred. **Reporting granularity is unchanged.** Like `forAll`, `forNone` runs inside a single Kotest test, so the whole table is one report node; per-row identity exists only in the failure message. Per-row test nodes require `withData`, not the table DSL. ## How to say it in an interview "`forNone` inverts the success condition of the table runner: the block must throw for every row, and a row whose block completes normally is the failure. Both runners are defined on thrown errors, not returned booleans — a boolean body asserts nothing, which makes `forAll` silently green and `forNone` fail for the wrong reason. And because `forNone` treats *any* throwable as satisfaction, I'd reach for `shouldThrow<T>` inside `forAll` when I care which exception the row produced."
- Why can a `forNone` table pass even though the code under test is completely broken?Because `forNone` counts a row as satisfied whenever its block throws, and it does not care which throwable. If a bug in the setup makes every row throw a `NullPointerException`, `forNone` sees every row failing the assertion and reports success. When the identity of the failure matters, use `shouldThrow<SpecificException>` inside a `forAll` so the assertion pins down both that it failed and how.
- When would you prefer `forAll` with a negated matcher over `forNone`?Whenever you want to be precise about the failure mode or want each row checked against something stronger than "anything went wrong". `forAll(t) { it shouldNotMatch emailRegex }` states the property directly and cannot be satisfied by an unrelated exception. `forNone` is nicer when the positive property is the readable one and you simply want to say no row satisfies it.
saying these in an interview costs you the question
- "forNone means the predicate must return false for every row" — the block is an assertion block; return values are ignored.
- "forNone fails when a row throws" — inverted: a row that throws is what forNone requires; a row that completes normally fails it.
- "forNone only counts AssertionError" — any throwable counts, which is why an NPE in setup can make it pass for the wrong reason.
- "forNone gives one report entry per row" — like forAll, the whole table runs inside a single test node.
- "forNone is the same function as the property-testing forNone/forAll" — the table DSL runners live in io.kotest.data and have a different contract.