1---2name: property-based-testing-with-kotest3description: Writes property-based tests using Kotest's kotest-property module. Identifies testable properties, designs generators, and configures PBT for Kotlin/JVM projects. Use when writing property-based tests, creating custom Arb generators, choosing property patterns (roundtrip, invariant, idempotence, oracle), debugging shrunk counterexamples, or integrating PBT into a Kotlin test suite alongside example-based tests.4---56# Property-Based Testing with Kotest78## Workflow: Writing a property-based test9101. **Decide if PBT fits.** Use PBT for functions with broad input spaces, clear11 invariants, round-trip operations, or many parameter combinations. Keep12 example-based tests for specific business scenarios and regression pinning.132. **Identify properties.** Pick from these patterns (most to least common):14 - **Roundtrip** — `decode(encode(x)) == x`. For serialize/parse/compress pairs.15 - **Invariant** — a measurable property is preserved (e.g., `sort` preserves size and elements).16 - **Idempotence** — `f(f(x)) == f(x)`. For trim, distinct, upsert, PUT.17 - **Oracle** — compare optimized implementation against a simple correct one.18 - **Hard to prove, easy to verify** — check output validity (e.g., prime factors multiply back).19 - **Commutativity** — different operation orders yield same result.20 - **Induction** — base case + recursive step.21 - **Metamorphic** — when correct output is unknown, test relationships between outputs under related inputs.22 Always identify **at least two** complementary properties per function under test.233. **Design generators.** Use `Arb` for random+edge-case generation (default),24 `Exhaustive` for small finite domains. Constrain generators at construction25 — don't rely on `filter()` or `assume()` (keep discard rate under 10%).26 For domain types, compose with `arbitrary { ... }` using `.bind()`.274. **Write the test.** Use `checkAll` with Kotest matchers (preferred over28 `forAll` with booleans). Default: 1,000 iterations.295. **Handle failures.** Analyze the shrunk counterexample. Convert it into a30 permanent example-based regression test. Keep the property test running to31 find future failures.3233## Dependency3435```kotlin36// build.gradle.kts37dependencies {38 testImplementation("io.kotest:kotest-property:$kotestVersion")39}40```4142## Key decisions4344- **`checkAll` vs `forAll`:** Use `checkAll` — richer error messages via matchers.45- **`Arb` vs `Exhaustive`:** Use `Arb` unless the domain is small and finite (enums, boolean).46- **Iteration count:** 1,000 (default) for local/CI. Use env var for nightly builds (10k+).47- **Shrinking:** Leave at default `Bounded(1000)`. Use `Unbounded` only when debugging.48- **Custom generators:** Always use `.bind()` inside `arbitrary {}`, never `kotlin.random.Random`.49- **Custom shrinkers:** Must preserve domain invariants. Invalid shrunk values cause false failures.50- **Seeds:** Let Kotest auto-persist failed seeds. Convert discovered failures to regression tests.5152## Edge cases5354- **Lossy conversions:** Roundtrip pattern fails if conversion loses precision (e.g., `Float`→`Double`). Verify losslessness or test a weaker property.55- **Cross-variable constraints:** Use `assume()` only when the constraint can't be expressed in the generator. Prefer restructuring (e.g., `val (larger, smaller) = if (a > b) a to b else b to a`).56- **Slow generators:** Don't reduce iteration count. Optimize the generator, split suites, or run long suites in nightly builds.57- **Non-determinism in SUT:** Pin seeds for debugging, but keep the main property test with random seeds.5859## Reference material6061- **Fundamentals and core API**: [references/01-fundamentals-and-core-api.md](references/01-fundamentals-and-core-api.md) — when to use PBT, `checkAll`/`forAll`, `Arb` vs `Exhaustive`62- **Property patterns**: [references/02-property-patterns.md](references/02-property-patterns.md) — the seven patterns with Kotlin examples63- **Generator design**: [references/03-generator-design.md](references/03-generator-design.md) — built-in Arbs, operations, `arbitrary {}`, domain composition64- **Shrinking, seeds, assumptions**: [references/04-shrinking-seeds-assumptions.md](references/04-shrinking-seeds-assumptions.md) — shrinking modes, custom shrinkers, seed persistence, `assume()`65- **Configuration and anti-patterns**: [references/05-configuration-and-anti-patterns.md](references/05-configuration-and-anti-patterns.md) — `PropTestConfig`, global settings, common mistakes66- **Advanced topics and strategy**: [references/06-advanced-topics-and-strategy.md](references/06-advanced-topics-and-strategy.md) — stateful/model-based, metamorphic, concurrency, CI/CD, adoption67- **Quick reference**: [references/07-quick-reference.md](references/07-quick-reference.md) — API cheat sheet, sources