Header menu logo testify

Configuration, Arbitraries, and Generators

This page is about the configurable part of property testing in Testify:

Think of property configuration in layers:

CheckConfig

CheckConfig is the place to shape the FsCheck run itself.

Useful entry points include:

Example:

let config =
    CheckConfig.defaultConfig
    |> CheckConfig.withMaxTest 25
    |> CheckConfig.withEndSize 20

The most useful knobs are:

Use replay helpers when you want to re-run a previously reported counterexample without guessing the failing case.

Arbitraries

Arbitraries builds FsCheck.Arbitrary<'T> values:

Example:

let pairArb =
    Arbitraries.tuple2
        (Arbitraries.from<int>)
        (Arbitraries.from<int>)

Then:

Check.result(
    CheckExpectation.equalToReference,
    (fun (a, b) -> a + b),
    <@ fun (a, b) -> a + b @>,
    arbitrary = pairArb)

Reach for Arbitraries when the property still has one natural 'Args, but the default generator space is too weak or too noisy.

Typical cases:

Generators

Generators builds raw FsCheck.Gen<'T> values when you want lower-level control before turning them into arbitraries.

Typical use cases:

Example:

let lengthControlled =
    Generators.listOfLength
        5
        (Generators.from<int>)

and then:

let arbitrary =
    Arbitraries.fromGen lengthControlled

Use Generators when:

Good rule of thumb:

Choosing Between arbitrary and resultBy

Use arbitrary = ... when:

Use resultBy / shouldBy when:

Practical Recipes

Run fewer, smaller tests while iterating

let quickConfig =
    CheckConfig.defaultConfig
    |> CheckConfig.withMaxTest 20
    |> CheckConfig.withEndSize 10

Replay a failing run from stored output

let replayConfig =
    CheckConfig.defaultConfig
    |> CheckConfig.withReplayString "Rnd=(123,456); Size=17"

Shape a property around one well-defined input model

let personArb =
    Generators.elements [ "Tony"; "Pepper"; "Rhodey" ]
    |> Generators.map (fun name -> { Name = name; Age = 48 })
    |> Arbitraries.fromGen

Use shouldBy when generation depends on earlier values

Check.shouldBy(
    (fun verify ->
        FsCheck.Prop.forAll Arbitraries.from<int> (fun n ->
            let length = abs n
            let xsArb =
                Arbitraries.fromGen (
                    FsCheck.Gen.listOfLength length FsCheck.Arb.generate<int>
                )

            FsCheck.Prop.forAll xsArb (fun xs ->
                verify (length, xs)))),
    CheckExpectation.isTrue,
    (fun _ -> true),
    <@ fun (length, xs) -> List.length xs = length @>)

Available Option Sources

When you see config and arbitrary in the API:

Expected helper sources:

Local vs Global Configuration

This page is about per-check property configuration.

For suite-wide defaults such as report formatting, hint packs, and default check-config transformations, continue with Global Configuration.

val config: obj
val pairArb: obj
Multiple items
val int: value: 'T -> int (requires member op_Explicit)

--------------------
type int = int32

--------------------
type int<'Measure> = int
val lengthControlled: obj
val arbitrary: obj
val quickConfig: obj
val replayConfig: obj
val personArb: obj
val abs: value: 'T -> 'T (requires member Abs)
Multiple items
module List from Microsoft.FSharp.Collections

--------------------
type List<'T> = | op_Nil | op_ColonColon of Head: 'T * Tail: 'T list interface IReadOnlyList<'T> interface IReadOnlyCollection<'T> interface IEnumerable interface IEnumerable<'T> member GetReverseIndex: rank: int * offset: int -> int member GetSlice: startIndex: int option * endIndex: int option -> 'T list static member Cons: head: 'T * tail: 'T list -> 'T list member Head: 'T with get member IsEmpty: bool with get member Item: index: int -> 'T with get ...
val length: list: 'T list -> int

Type something to start searching.