PropertyDamage.ExUnit (PropertyDamage v0.2.0)

View Source

ExUnit integration for PropertyDamage property tests.

This module provides macros for writing property-based tests that integrate seamlessly with ExUnit. Tests can be defined using module attributes for configuration, with individual test overrides as needed.

Usage

defmodule MySystemTest do
  use ExUnit.Case
  use PropertyDamage.ExUnit

  property_damage "system maintains invariants",
    model: MyApp.TestModel,
    adapter: MyApp.TestAdapter,
    max_commands: 50,
    max_runs: 100

  property_damage "handles concurrent access",
    model: MyApp.TestModel,
    adapter: MyApp.ConcurrentAdapter,
    max_commands: 100
end

Test Options

Options passed to property_damage/2:

Required:

  • :model - Model module (required)
  • :adapter - Adapter module (required)

Optional: every other PropertyDamage.run/1 option is forwarded as-is, including:

  • :max_commands - Max commands per sequence (default: 50)
  • :max_runs - Number of test sequences (default: 100)
  • :seed - Fixed seed for reproducibility
  • :injector_adapters - List of injector adapter modules (default: [])
  • :shrink - Whether to shrink failures (default: true)
  • :validate - Whether to validate config (default: true)
  • :adapter_config - Config passed to adapter.setup/1 (default: %{})
  • :verbose, :assertion_mode, :branching, :stutter, :external_markers, :on_failure, and the rest of the run/1 surface

Defaults are applied by run/1 itself; a nil :seed is dropped so the run picks a random seed.

Failure Formatting

When a property test fails, the output includes:

  1. The seed for reproducing the failure
  2. The original command sequence
  3. The shrunk (minimal) command sequence
  4. The failure reason
  5. Instructions for reproducing with the same seed

Example Failure Output

1) property system maintains invariants
   Failure with seed: 12345

   Original sequence (5 commands):
     [%CreateItem{...}, %ViewItem{...}, ...]

   Shrunk sequence (2 commands):
     [%CreateItem{quantity: 101}]

   Failed at command #0:
     {:check_failed, :quantity_limit, "Quantity 101 exceeds limit"}

   Reproduce with: seed: 12345

Summary

Functions

Use this module to enable PropertyDamage test macros.

Assemble the options forwarded to PropertyDamage.run/1 for a property_damage/2 test.

Format a failure report for ExUnit output.

Define a property-based test.

Functions

__using__(opts)

(macro)

Use this module to enable PropertyDamage test macros.

Requires use ExUnit.Case to be called first.

Example

defmodule MyTest do
  use ExUnit.Case
  use PropertyDamage.ExUnit

  @model MyModel
  @adapter MyAdapter

  property_damage "test name" do
    max_runs: 10
  end
end

build_run_opts(opts)

@spec build_run_opts(keyword()) :: keyword()

Assemble the options forwarded to PropertyDamage.run/1 for a property_damage/2 test.

:model and :adapter are required (a missing one raises KeyError with a clear message); every other option is forwarded verbatim, so the full run/1 surface (verbose:, assertion_mode:, branching:, stutter:, external_markers:, on_failure:, ...) is reachable from the ExUnit macro. run/1 validates the result and applies its own defaults for any omitted option, so this function deliberately does not re-specify them. A nil :seed is dropped so run/1 picks a random seed.

format_failure(report)

@spec format_failure(PropertyDamage.FailureReport.t()) :: String.t()

Format a failure report for ExUnit output.

Produces human-readable output with all relevant information for debugging and reproducing the failure.

property_damage(name, opts \\ [])

(macro)

Define a property-based test.

Creates an ExUnit test that generates and executes command sequences, checking for invariant violations.

Parameters

  • name - Test name (string)
  • opts - Options keyword list (see module docs for available options)

Examples

# Basic usage
property_damage "basic test",
  model: MyModel,
  adapter: MyAdapter

# With options
property_damage "custom test",
  model: MyModel,
  adapter: MyAdapter,
  max_commands: 100,
  max_runs: 50

# With fixed seed for reproduction
property_damage "reproducible test",
  model: MyModel,
  adapter: MyAdapter,
  seed: 12345