I'm trying to implement a custom Arbitrary
that generates glob syntax patterns like a*c?
. I think my implementation is correct, it's just that, when running the test with Xunit, FsCheck doesn't seem to be using the custom arbitrary Pattern
to generate the test data. When I use LINQPad however everything works as expected. Here's the code:
open Xunit
open FsCheck
type Pattern = Pattern of string with
static member op_Explicit(Pattern s) = s
type MyArbitraries =
static member Pattern() =
(['a'..'c']@['?'; '*'])
|> Gen.elements
|> Gen.nonEmptyListOf
|> Gen.map (List.map string >> List.fold (+) "")
|> Arb.fromGen
|> Arb.convert Pattern string
Arb.register<MyArbitraries>() |> ignore
[<Fact>]
let test () =
let prop (Pattern p) = p.Length = 0
Check.QuickThrowOnFailure prop
This is the output:
Falsifiable, after 2 tests (0 shrinks) (StdGen (1884571966,296370531)): Original: Pattern null with exception: System.NullReferenceException ...
And here is the code I'm running in LINQPad along with the output:
open FsCheck
type Pattern = Pattern of string with
static member op_Explicit(Pattern s) = s
type MyArbitraries =
static member Pattern() =
(['a'..'c']@['?'; '*'])
|> Gen.elements
|> Gen.nonEmptyListOf
|> Gen.map (List.map string >> List.fold (+) "")
|> Arb.fromGen
|> Arb.convert Pattern string
Arb.register<MyArbitraries>() |> ignore
let prop (Pattern p) = p.Length = 0
Check.Quick prop
Falsifiable, after 1 test (0 shrinks) (StdGen (1148389153,296370531)): Original: Pattern "a*"
As you can see FsCheck generates a null
value for the Pattern
in the Xunit test although I'm using Gen.elements
and Gen.nonEmptyListOf
to control the test data. Also, when I run it a couple times, I'm seeing test patterns that are out of the specified character range. In LINQPad those patterns are generated correctly. I also tested the same with a regular F# console application in Visual Studio 2017 and there the custom Arbitrary
works as expected as well.
What is going wrong? Is FsCheck falling back to the default string
Arbitrary
when running in Xunit?
You can clone this repo to see for yourself: https://github.com/bert2/GlobMatcher
(I don't want to use Prop.forAll
, because each test will have multiple custom Arbitrary
s and Prop.forAll
doesn't go well with that. As far as I know I can only tuple them up, because the F# version of Prop.forAll
only accepts a single Arbitrary
.)
Property
attribute to registerMyArbitraries
did the trick. I wasn't using the combinators, because each test needs multiple custom arbitraries, butProp.forAll
only accepts one. At least in F#; the C# versionProp.ForAll
has overloads for multiple arbitraries. Also I like the way howFsCheck.Xunit.Property
s use the test function's parameters to create the needed generators. IMO that's more readable than usingProp.forAll
. – Karee