Using custom generators in FsCheck (xUnit variant)

Hello all.
I’m still having issues grokking FsCheck following my previous question.
With a lot of SO help I now got the following code to define and register two custom data generators of my domain:

let genString15 =
    Gen.sized (fun s -> Gen.resize (min s 15) Arb.generate<NonNull<string>>)
    |> Gen.map (fun (NonNull str) -> str)

let genNonNegativeof f = gen {
    let! (NonNegativeInt n) = Arb.generate<NonNegativeInt>
    return (f n)
}

let genNonLegendaryItem = gen {
    let! name = Gen.map Name genString15
    let! quality = genNonNegativeof Quality
    let! shelflife = genNonNegativeof Days
    let! style = Gen.elements [Plain; Conjured]
    return {
        Name = name
        Quality = quality
        ShelfLife = shelflife
        Style = style
    }
}

let genLegendaryItem = genNonLegendaryItem |> Gen.map (fun item -> {item with Style = Legendary})

type NonLegendaryItem = NonLegendaryItem of Item
type LegendaryItem = LegendaryItem of Item

type Generators =
    static member NonLegendaryItem() =
        Gen.map NonLegendaryItem genNonLegendaryItem
    static member LegendaryItem() =
        Gen.map LegendaryItem genLegendaryItem

Arb.register<Generators>()

What I am trying now is to use those generators for my test case(s) using xUnit.Net (assume I’ve opened all the necessary libraries and the following handleDayOver: Int -> Item -> Item, where Item is also already defined and the function itself has been Example-based unit tested and is correct):

[<Property>]
let ``Some descriptive name``(item: NonLegendaryItem) =
    let nli = handleDayOver 100 item
    nli.Quality = Quality 0

This snippet won’t compile since item is indeed NonLegendaryItem whereas the function takes a “simple” Item.

Trying this instead:

[<Property>]
let ``Some descriptive name`` (NonLegendaryItem item) =
    let nli = handleDayOver 100 item
    nli.Quality = Quality 0

Fails in the sense that now item is not NonLegendaryItem but a “simple” Item and thus it may be of Styles that invalidate the invariant (i.e. Legendary that never drops to 0).

I don’t want to change my implementation of handleDayOver since the (Non)LegendaryItem types are pure testing related with no bearing on the actual application, but I can’t figure out how to use them so that the test item is forced as a (Non)LegendaryItem and compiles without a type-error?

Thanks!

Arb.register with xUnit.net will, generally speaking, only cause you pain and suffering. My best advice is to avoid it. Inline your Arbitraries instead:

I’ve had many discussions with Kurt Schelfthout (the creator of FsCheck) on this issue, and I’m not sure that he agrees completely with me, so take this as nothing more than my opinion. It is, however, quite clear: I think that Arb.register should be deprecated from FsCheck.

You can write any FsCheck property in xUnit.net without Arb.register. With its reliance on imperative state mutation, it’s not a particularly functional way of doing things, and you also lose static type checking when you use it.

1 Like