How do you configure iteration count, edge-case injection, seeds, and discards in Kotest property tests, and why do edge cases matter?
answer
- PropTestConfig: iterations, seed, edgeConfig, maxDiscardPercentage
- Default 1000 iterations
- seed printed on failure → reproduce
- Edge cases = 0, MIN/MAX, empty, blank, null
- Bound the Arb instead of filtering to avoid discards
basics
~20 sYou can set how many random inputs to try, control the share of special edge values, fix a seed so failures repeat, and limit how many inputs may be skipped. Edge cases catch boundary bugs like zero or empty.
solid answer
~30 sTuning happens via `PropTestConfig` passed to `checkAll`/`forAll`, or via inline params. Key knobs: `iterations` (default ~1000) controls how many samples run; `seed` fixes the RandomSource so a failure reproduces exactly; `edgeConfig`/`PropTestConfig(edgeConfig = EdgeConfig(edgecasesGenerationProbability = …))` controls how often injected edge cases appear; `maxDiscardPercentage` limits how many filtered/`Assumptions`-rejected samples are tolerated before failing with 'too many discards'. Edge cases matter because Arbs deterministically inject boundary values (0, MIN/MAX, empty, blank, null via `.orNull()`) where off-by-one and overflow bugs cluster — random sampling alone rarely hits exact boundaries. You can also constrain ranges (`Arb.int(0..100)`) instead of filtering to avoid discards.
code
kotlin · 16 linesimport io.kotest.property.*
import io.kotest.property.arbitrary.int
import io.kotest.matchers.shouldBe
suspend fun configured() {
checkAll(
PropTestConfig(
iterations = 3000,
maxDiscardPercentage = 15,
edgeConfig = EdgeConfig(edgecasesGenerationProbability = 0.25)
),
Arb.int(0..10_000)
) { n ->
(n + 0) shouldBe n
}
}go deeper
Knows you can set iteration count and that a seed lets a failure repeat.
Uses PropTestConfig fields (iterations, seed, maxDiscardPercentage) and explains why edge cases catch boundary bugs.
Diagnoses 'too many discards' by narrowing generators, tunes edgecasesGenerationProbability, and sets project-wide defaults.
Establishes suite-wide PropTestConfig policy (iteration budgets, seed logging for CI repro, discard limits) balancing speed and bug-finding power.
## Where configuration lives Property runners accept a **`PropTestConfig`** (or inline arguments) controlling the run: ```kotlin import io.kotest.property.* import io.kotest.property.arbitrary.int checkAll( PropTestConfig(iterations = 5000, seed = 42L), Arb.int() ) { n -> /* property */ } ``` You can also pass `checkAll(iterations = 5000, Arb.int()) { … }` directly. ## Iterations - **`iterations`** = number of samples to run. **Default is 1000** for a single generator; with multiple generators Kotest may compute a count based on the generators. More iterations = more confidence but slower. ## Seeds and reproducibility - **`seed`** fixes the `RandomSource`. On failure Kotest prints the seed it used; set that seed to deterministically reproduce the exact sequence of generated values. Essential for debugging CI-only failures. Don't pin a seed permanently in normal runs — that defeats randomized coverage. ## Edge-case injection Every `Arb` defines **edge cases**: deterministic boundary values yielded *before/among* random ones. Examples: - `Arb.int()` → `0`, `Int.MIN_VALUE`, `Int.MAX_VALUE` - `Arb.string()` → empty, blank - `Arb.list(...)` → empty list - `.orNull()` → `null` Why they matter: bugs cluster at **boundaries** (off-by-one, overflow, empty-collection NPEs). Pure random sampling almost never lands on `Int.MAX_VALUE` exactly, so without injection these bugs stay hidden. Control frequency via `EdgeConfig`: ```kotlin PropTestConfig( edgeConfig = EdgeConfig(edgecasesGenerationProbability = 0.3) ) ``` A probability of `0.0` effectively disables edge injection; higher values test boundaries more aggressively. ## Discards (assumptions / filtering) When you use `filter` on an Arb, or call **`Assumptions`** (e.g. an inline guard that returns early / `assume(cond)`), rejected samples are **discarded**. Too many discards means your generator wastes effort. Kotest caps this via: ```kotlin PropTestConfig(maxDiscardPercentage = 20) ``` Exceeding it throws a 'too many discards' error, signaling you should **narrow the generator** instead of filtering — e.g. `Arb.int(0..100)` rather than `Arb.int().filter { it in 0..100 }`. ## Putting it together ```kotlin checkAll( PropTestConfig( iterations = 2000, seed = null, // random; printed on failure maxDiscardPercentage = 10, edgeConfig = EdgeConfig(edgecasesGenerationProbability = 0.2) ), Arb.int(0..1000) ) { n -> (n * 2) shouldBe (n + n) } ``` ## Global defaults Project-wide defaults (iterations, etc.) can be set via Kotest's `AbstractProjectConfig` so you don't repeat config per test.
- You get a 'too many discards' failure. What's the fix?Stop filtering; construct a generator that only produces valid values (e.g. Arb.int(0..100) or Arb.bind with constrained components) so few samples are rejected.
- Why not hardcode a seed in every property test?A fixed seed makes the test deterministic but eliminates the randomized exploration that finds new bugs; pin a seed only to reproduce a specific failure.
saying these in an interview costs you the question
- Solves slow/failing generators by raising maxDiscardPercentage instead of fixing the generator
- Doesn't know edge cases are injected deterministically
- Pins a seed permanently, killing coverage
- Cannot name PropTestConfig or any of its fields