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 ...
endIf 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
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.
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 ofStreamDatagenerators, one per function argument.:name(optional) — a string used as the property's description. Defaults to"contract_holds <source>".
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 withcontract_holds/2.
The oracle is invariants and per-function contracts
The runner also checks each operation's own
@pre/@post/checkcontracts as it goes, so a struct module with per-function contracts but no@invariantis 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 withcontract_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>".