Expecto FsCheck on exception when generating row
I am trying to learn how to use FsCheck correctly and integrate it with Expecto at the moment. I can run property tests if I use the default FsCheck configuration, but when I try to use my own generator it throws an exception.
Here is my generator
type NameGen() =
static member Name() =
Arb.generate<string * string>
|> Gen.where (fun (firstName, lastName) ->
firstName.Length > 0 && lastName.Length > 0
)
|> Gen.map (fun (first, last) -> sprintf "%s %s" first last)
|> Arb.fromGen
|> Arb.convert string id
And I'm trying to use it like this:
let config = { FsCheckConfig.defaultConfig with arbitrary = [typeof<NameGen>] }
let propertyTests input =
let output = toInitials input
output.EndsWith(".")
testPropertyWithConfig config "Must end with period" propertyTests
The exception is thrown before it even hits the function Gen.where
What am I doing wrong? Thanks to
source to share
You are trying to use the FsCheck string generator to override how its string generator works, but when you do, it will recursively call itself until it runs out of stack space. This is a known issue: https://github.com/fscheck/FsCheck/issues/109
Does this alternative help?
type NameGen =
static member Name () =
Arb.Default.NonEmptyString().Generator
|> Gen.map (fun (NonEmptyString s) -> s)
|> Gen.two
|> Gen.map (fun (first, last) -> sprintf "%s %s" first last)
|> Arb.fromGen
source to share
You are defining a new generator for a string of type, but inside that you are using a generator for string * string
which uses a generator for string
. FsCheck, unfortunately, seems to keep generators in a globally mutable state (perhaps with good reason?), And I think that means that the generator keeps calling itself until.
This can be solved by specifying a generator for a custom wrapper type instead of a simple string (shown below).
The next problem you will run into will be a blank exception reference. The initial generated string might be null
and you are trying to access the property .Length
. This can be solved by using instead a function String.length
that returns 0
for null
.
With these changes, your generator looks like this:
type Name = Name of string
type NameGen() =
static member Name() =
Arb.generate<string * string>
|> Gen.where (fun (firstName, lastName) ->
String.length firstName > 0 && String.length lastName > 0
)
|> Gen.map (fun (first, last) -> sprintf "%s %s" first last)
|> Arb.fromGen
|> Arb.convert Name (fun (Name n) -> n)
And your property needs a little modification:
let propertyTests (Name input) =
let output = toInitials input
output.EndsWith(".")
source to share