Get the FREE Ultimate OpenClaw Setup Guide →

property-based-testing-with-kotest

npx machina-cli add skill msewell/agent-stuff/property-based-testing-with-kotest --openclaw
Files (1)
SKILL.md
4.8 KB

Property-Based Testing with Kotest

Workflow: Writing a property-based test

  1. 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.
  2. Identify properties. Pick from these patterns (most to least common):
    • Roundtripdecode(encode(x)) == x. For serialize/parse/compress pairs.
    • Invariant — a measurable property is preserved (e.g., sort preserves size and elements).
    • Idempotencef(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.
  3. Design generators. Use Arb for random+edge-case generation (default), Exhaustive for small finite domains. Constrain generators at construction — don't rely on filter() or assume() (keep discard rate under 10%). For domain types, compose with arbitrary { ... } using .bind().
  4. Write the test. Use checkAll with Kotest matchers (preferred over forAll with booleans). Default: 1,000 iterations.
  5. 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

  • checkAll vs forAll: Use checkAll — richer error messages via matchers.
  • Arb vs Exhaustive: Use Arb unless 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). Use Unbounded only when debugging.
  • Custom generators: Always use .bind() inside arbitrary {}, never kotlin.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., FloatDouble). 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

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

  1. Step 1: Decide if PBT fits and identify properties.
  2. Step 2: Design generators with Arb and bind inside arbitrary {}; choose between Arb and Exhaustive.
  3. 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

Add this skill to your agents

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.

Sponsor this space

Reach thousands of developers