Header menu logo testify

Property Checks

This page covers the generated-input side of Testify:

Use this layer when you want to compare a tested implementation against a trusted reference over many generated inputs.

Why This Style Is Powerful

Check is not just “FsCheck with another name.”

Its sweet spot is:

That makes it especially useful for:

Check.result

Check.result is the structured, non-throwing property runner.

let result =
    Check.result(
        CheckExpectation.equalToReference,
        (fun xs -> xs |> List.rev |> List.rev),
        <@ List.rev >> List.rev @>)

Optional named parameters:

let configured =
    Check.result(
        CheckExpectation.equalToReference,
        (fun x -> x),
        <@ fun x -> x @>,
        config = CheckConfig.withMaxTest 25,
        arbitrary = Arbitraries.from<int>)

Use result when you want to:

Check.should

Check.should uses the same engine, but raises immediately on failure.

Check.should(
    CheckExpectation.equalToReference,
    List.sort,
    <@ List.sort @>)

Use should when:

Check.resultBy and Check.shouldBy

Use the By variants when one plain Arbitrary<'Args> is not enough and you want to build the surrounding property yourself.

Check.shouldBy(
    (fun verify ->
        FsCheck.Prop.forAll Arbitraries.from<int> (fun n ->
            let length = abs n
            let arb = Arbitraries.fromGen (FsCheck.Gen.listOfLength length FsCheck.Arb.generate<int>)
            FsCheck.Prop.forAll arb (fun xs ->
                verify (length, xs)))),
    CheckExpectation.isTrue,
    (fun _ -> true),
    <@ fun (expectedLength, xs) -> List.length xs = expectedLength @>)

Important rule:

Decision Guide

Start with the simplest thing that expresses the property:

Use Check.result / Check.should

When:

Add arbitrary = ...

When:

Move to resultBy / shouldBy

When:

Operators

The Check operator layer is intentionally small:

open Testify.CheckOperators

<@ List.rev >> List.rev @> |=> id
<@ List.rev >> List.rev @> |=>> id |> ignore

Advanced callback-built bool property:

<@ fun (n, xs) -> List.length xs = n @>
|?> (fun verify ->
        FsCheck.Prop.forAll Arbitraries.from<int> (fun n ->
            let length = abs n
            let arb = Arbitraries.fromGen (FsCheck.Gen.listOfLength length FsCheck.Arb.generate<int>)
            FsCheck.Prop.forAll arb (fun xs ->
                verify (length, xs))))

Operators are deliberately thin:

If you need custom config, a custom arbitrary, or a more explicit explanation, prefer the named Check API.

Common Expectation Shapes

Start with:

Then reach for:

Shrinking, Replay, and Debugging

When a property fails, Testify preserves more than a boolean false:

Typical replay flow:

let result =
    Check.result(
        CheckExpectation.equalToReference,
        (fun x -> x + 1),
        <@ fun x -> x + 2 @>)

match result with
| Failed failure ->
    match failure.TryGetReplayConfig() with
    | Some replayConfig ->
        let replayed =
            Check.result(
                CheckExpectation.equalToReference,
                (fun x -> x + 1),
                <@ fun x -> x + 2 @>,
                config = replayConfig)
        printfn "%s" (Check.toDisplayString replayed)
    | None ->
        ()
| _ ->
    ()

When This Style Shines Most

Property checks are especially useful for:

For exact configuration helpers and the full callback contract, continue with Configuration, Arbitraries, and Generators.

val result: obj
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 rev: list: 'T list -> 'T list
val configured: obj
Multiple items
val int: value: 'T -> int (requires member op_Explicit)

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

--------------------
type int<'Measure> = int
val sort: list: 'T list -> 'T list (requires comparison)
val abs: value: 'T -> 'T (requires member Abs)
val length: list: 'T list -> int
val id: x: 'T -> 'T
val ignore: value: 'T -> unit
val n: int
val xs: 'a list
val result: 'a
val failure: 'a
union case Option.Some: Value: 'T -> Option<'T>
val replayConfig: 'a
val replayed: 'a
val printfn: format: Printf.TextWriterFormat<'T> -> 'T
union case Option.None: Option<'T>

Type something to start searching.