property-based-testing-with-kotest
npx machina-cli add skill msewell/agent-stuff/property-based-testing-with-kotest --openclawProperty-Based Testing with Kotest
Workflow: Writing a property-based test
- Decide if PBT fits. Use PBT for functions with broad input spaces, clear invariants, round-trip operations, or many parameter combinations. Keep example-based tests for specific business scenarios and regression pinning.
- Identify properties. Pick from these patterns (most to least common):
- Roundtrip —
decode(encode(x)) == x. For serialize/parse/compress pairs. - Invariant — a measurable property is preserved (e.g.,
sortpreserves size and elements). - Idempotence —
f(f(x)) == f(x). For trim, distinct, upsert, PUT. - Oracle — compare optimized implementation against a simple correct one.
- Hard to prove, easy to verify — check output validity (e.g., prime factors multiply back).
- Commutativity — different operation orders yield same result.
- Induction — base case + recursive step.
- Metamorphic — when correct output is unknown, test relationships between outputs under related inputs. Always identify at least two complementary properties per function under test.
- Roundtrip —
- Design generators. Use
Arbfor random+edge-case generation (default),Exhaustivefor small finite domains. Constrain generators at construction — don't rely onfilter()orassume()(keep discard rate under 10%). For domain types, compose witharbitrary { ... }using.bind(). - Write the test. Use
checkAllwith Kotest matchers (preferred overforAllwith booleans). Default: 1,000 iterations. - Handle failures. Analyze the shrunk counterexample. Convert it into a permanent example-based regression test. Keep the property test running to find future failures.
Dependency
// build.gradle.kts
dependencies {
testImplementation("io.kotest:kotest-property:$kotestVersion")
}
Key decisions
checkAllvsforAll: UsecheckAll— richer error messages via matchers.ArbvsExhaustive: UseArbunless the domain is small and finite (enums, boolean).- Iteration count: 1,000 (default) for local/CI. Use env var for nightly builds (10k+).
- Shrinking: Leave at default
Bounded(1000). UseUnboundedonly when debugging. - Custom generators: Always use
.bind()insidearbitrary {}, neverkotlin.random.Random. - Custom shrinkers: Must preserve domain invariants. Invalid shrunk values cause false failures.
- Seeds: Let Kotest auto-persist failed seeds. Convert discovered failures to regression tests.
Edge cases
- Lossy conversions: Roundtrip pattern fails if conversion loses precision (e.g.,
Float→Double). Verify losslessness or test a weaker property. - 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). - Slow generators: Don't reduce iteration count. Optimize the generator, split suites, or run long suites in nightly builds.
- Non-determinism in SUT: Pin seeds for debugging, but keep the main property test with random seeds.
Reference material
- Fundamentals and core API: references/01-fundamentals-and-core-api.md — when to use PBT,
checkAll/forAll,ArbvsExhaustive - Property patterns: references/02-property-patterns.md — the seven patterns with Kotlin examples
- Generator design: references/03-generator-design.md — built-in Arbs, operations,
arbitrary {}, domain composition - Shrinking, seeds, assumptions: references/04-shrinking-seeds-assumptions.md — shrinking modes, custom shrinkers, seed persistence,
assume() - Configuration and anti-patterns: references/05-configuration-and-anti-patterns.md —
PropTestConfig, global settings, common mistakes - Advanced topics and strategy: references/06-advanced-topics-and-strategy.md — stateful/model-based, metamorphic, concurrency, CI/CD, adoption
- Quick reference: references/07-quick-reference.md — API cheat sheet, sources
Source
git clone https://github.com/msewell/agent-stuff/blob/main/skills/property-based-testing-with-kotest/SKILL.mdView on GitHub Overview
Property-Based Testing with Kotest lets you specify general properties of your Kotlin/JVM code and automatically explore inputs with the kotest-property module. It guides you to identify testable properties, design Arb generators, and configure PBT alongside traditional example-based tests. This approach surfaces edge cases and enforces invariants across diverse inputs.
How This Skill Works
Start by deciding if PBT fits the function under test, then identify properties and craft generators using Arb or Exhaustive. Write tests with checkAll, typically running around 1,000 iterations by default, and use Kotest matchers for richer failure messages. When failures occur, inspect the shrunk counterexample and convert it into a regression test to guard future changes.
When to Use It
- When the function has a broad input space and clear invariants.
- When you need custom Arb generators for domain specific data.
- When selecting property patterns such as roundtrip, invariant, idempotence, and oracle.
- When debugging shrunk counterexamples to understand failures.
- When integrating PBT into a Kotlin test suite alongside example-based tests.
Quick Start
- Step 1: Decide if PBT fits and identify properties.
- Step 2: Design generators with Arb and bind inside arbitrary {}; choose between Arb and Exhaustive.
- Step 3: Write the test with checkAll and run; inspect shrinks and convert failures to regression tests.
Best Practices
- Use checkAll with Kotest matchers for richer error messages.
- Prefer Arb over Exhaustive unless the domain is small and finite.
- Default to 1,000 iterations; raise for nightly builds via environment vars.
- Keep shrinking bounded (Bounded(1000)); use Unbounded only for debugging.
- Always bind custom generators inside arbitrary {}; avoid kotlin.random.Random.
Example Use Cases
- Roundtrip: decode(encode(x)) == x for serializers.
- Invariant: sort preserves size and elements.
- Idempotence: f(f(x)) == f(x) for trim or upsert.
- Oracle: compare optimized implementation against a simple correct one.
- Metamorphic tests to relate outputs across related inputs when outputs are unknown.
Frequently Asked Questions
Related Skills
creating-c4-diagrams
msewell/agent-stuff
Creates, reviews, and interprets C4 software architecture diagrams (System Context, Container, Component, Dynamic, Deployment). Produces Structurizr DSL or Mermaid diagram code following C4 model best practices. Use when creating architecture diagrams for a system, reviewing existing C4 diagrams for correctness and anti-patterns, generating Structurizr DSL workspaces, producing Mermaid C4 diagrams for READMEs, or using C4 diagrams as context for design decisions, code generation, risk analysis, or onboarding.
arazzo-specification
msewell/agent-stuff
Guides writing, reviewing, and modifying Arazzo workflow specifications (OpenAPI Initiative standard for multi-step API workflows). Use when creating Arazzo documents from scratch, adding steps or workflows to existing specs, reviewing Arazzo files for correctness, or generating API workflow definitions. Covers document structure, runtime expressions, success criteria, control flow, data threading, reusable components, workflow composition, AI agent integration, and validation.
kotlin-functional-programming
msewell/agent-stuff
Guides writing idiomatic, functional-style Kotlin code using built-in language features. Use when asked to write, review, or refactor Kotlin code for immutability, pure functions, sealed types, error handling, collections, coroutines, or functional architecture patterns.
reducing-coupling
msewell/agent-stuff
Analyzes a codebase scope for coupling issues, diagnoses coupling types using the Connascence framework, and proposes a comprehensive refactoring plan with concrete code changes. Use when asked to find coupling, reduce dependencies, decouple modules, or improve modularity in a codebase.
mermaid-sequence-diagrams
msewell/agent-stuff
Generates, reviews, and fixes Mermaid sequence diagrams following syntax rules and best practices. Use when creating sequence diagrams from system descriptions, reviewing existing Mermaid sequence diagrams for correctness, fixing parse errors, or refactoring large diagrams into focused sub-diagrams. Covers participants, arrows, activations, control flow, notes, styling, and common anti-patterns.
making-invalid-states-unrepresentable
msewell/agent-stuff
Analyzes existing code and guides new type design to make invalid states unrepresentable using type system techniques such as sum types, newtypes, typestate, branded types, and parse-don't-validate. Use when reviewing code for invalid-state bugs, refactoring types to eliminate impossible states, designing domain models, or applying compile-time correctness patterns. Language-agnostic.