View Source Bond.PropertyTest (Bond v1.0.0-rc.3)

Property-based testing helpers that drive Bond-contracted functions with random inputs.

Bond contracts (@pre, @post, check/1,2, @invariant) are runtime predicates that already encode what "correct" looks like. Property-based testing usually has two hard parts — generating inputs, and writing the oracle that distinguishes right from wrong outputs. With Bond, the oracle is already there at every call site; PBT just feeds random inputs in and lets the existing instrumentation raise on any violation.

Bond.PropertyTest adds two macros, one per testing shape:

  • contract_holds/2 — single function. Pass a function reference and a list of generators (one per argument). The macro calls the function with random inputs; any contract violation fails the property and StreamData shrinks to a minimal counterexample.

    contract_holds &Math.sqrt/1, args: [StreamData.float(min: 0.0)]
  • invariants_hold/2 — stateful module sequence. Pass a struct module plus constructor / transformer / observer specs. The macro generates random sequences of operations over the struct and runs them; the module's @invariants (plus any per-function contracts) are the oracle across every reachable state.

    invariants_hold BoundedStack,
      constructors: [{:new, [StreamData.integer(1..100)]}],
      transformers: [{:push, [StreamData.term()]}, {:pop, []}],
      observers:    [{:size, []}, {:peek, []}]

Setup

Bond.PropertyTest depends on stream_data. It's listed as an optional dependency in bond's mix file, so users opting into PBT add it to their own deps:

{:stream_data, "~> 1.0", only: [:dev, :test]}

Then in a test file:

defmodule MyTest do
  use ExUnit.Case
  use Bond.PropertyTest

  # contract_holds ... / invariants_hold ...
end

If stream_data is not available at compile time, use Bond.PropertyTest raises a CompileError with instructions to add the dep.

Summary

Functions

When used in an ExUnit test module, brings in ExUnitProperties (for the underlying property/2 and check all macros) and imports Bond.PropertyTest so contract_holds and invariants_hold are available.

Generates an ExUnit property that calls a single function with random arguments and verifies that Bond's contracts (preconditions, postconditions, checks, invariants) are all satisfied.

Generates an ExUnit property that runs random sequences of operations over a struct module and verifies that the module's @invariants (plus any per-function contracts) hold across every reachable state.

Functions

Link to this macro

__using__(opts)

View Source (macro)

When used in an ExUnit test module, brings in ExUnitProperties (for the underlying property/2 and check all macros) and imports Bond.PropertyTest so contract_holds and invariants_hold are available.

Raises a CompileError at the use site if stream_data isn't available — see the module docs.

Link to this macro

contract_holds(fun, opts)

View Source (macro)

Generates an ExUnit property that calls a single function with random arguments and verifies that Bond's contracts (preconditions, postconditions, checks, invariants) are all satisfied.

Pass a function reference and a list of generators, one per argument:

contract_holds &Math.sqrt/1, args: [StreamData.float(min: 0.0)]

The macro expands to a property block. On each iteration it generates one value per argument and calls the function. Any contract violation raised by Bond's runtime instrumentation fails the property; StreamData then shrinks to the minimal counterexample.

Useful for catching edge cases your example-based tests didn't cover. The function's contracts are the oracle — no separate assertion is needed.

For stateful testing over a struct module — random sequences of operations checked against the module's @invariants — see invariants_hold/2.

Options

  • :args (required) — list of StreamData generators, one per function argument.
  • :name (optional) — a string used as the property's description. Defaults to "contract_holds <source>".
Link to this macro

invariants_hold(module, opts)

View Source (macro)

Generates an ExUnit property that runs random sequences of operations over a struct module and verifies that the module's @invariants (plus any per-function contracts) hold across every reachable state.

This is Bond's stateful, sequence-based property testing. The invariants are a free oracle — they hold at every entry and exit, so there's no need to write an explicit per-operation model of expected behaviour, which is what makes stateful PBT cheap here.

Pass a struct module plus constructor, transformer, and observer specs. The macro generates random sequences of operations over the struct, threads state through them, and runs them.

invariants_hold BoundedStack,
  constructors: [{:new, [StreamData.integer(1..100)]}],
  transformers: [{:push, [StreamData.term()]}, {:pop, []}],
  observers:    [{:size, []}, {:peek, []}]

Each spec is a list of {fun_name, [arg_generators]} tuples:

  • Constructor — produces an initial struct. Called first in every sequence.
  • Transformer — takes the current struct as its first argument plus generated args, returns a new struct (%Mod{} or {:ok, %Mod{}}). Advances the state.
  • Observer — takes the current struct plus generated args, returns anything. Doesn't advance state. The pre-invariant still fires on entry.

Return shape rules for constructors and transformers:

  • %Mod{} — becomes the new state.
  • {:ok, %Mod{}} — same; the wrapper is stripped.
  • {:error, _} — terminates the sequence cleanly (the property passes; an operation that refuses is not a contract violation).
  • Anything else raises an ArgumentError; wrap your function or test it with contract_holds/2.

The oracle is invariants and per-function contracts

The runner also checks each operation's own @pre/@post/check contracts as it goes, so a struct module with per-function contracts but no @invariant is still meaningfully exercised. Invariants are the headline because they're what make the sequence form pull its weight — a module with no invariants buys little over testing each function with contract_holds/2.

Options

  • :constructors (required, non-empty) — list of {fun_name, [arg_generators]}.
  • :transformers (optional, default []) — same shape; state threaded in as the first argument.
  • :observers (optional, default []) — same shape; state passed but not advanced.
  • :name (optional) — a string used as the property's description. Defaults to "invariants_hold <module>".