Search code examples
f#xunitfscheck

Removing usage of Arb.registerByType from Xunit test using FsCheck


I am testing a round-trip of a Thoth.Json Encoder / Decoder pair.

It looks like this:

type CustomArbitrary =
  static member String() =
    Arb.Default.String()
    |> Arb.filter (not << isNull)

[<Fact>]
let ``Encode.foo Decode.foo round-trip`` () =
  let roundTrip (x : Foo) =
    let json =
      x
      |> Encode.foo
      |> Encode.toString 2

    let decoded =
      json
      |> Decode.fromString Decode.foo

    Ok x = decoded

  // Necessary?
  Arb.registerByType (typeof<CustomArbitrary>) |> ignore

  Check.QuickThrowOnFailure (roundTrip)

The test fails if I do not filter out null values for System.String. However, null is not a proper value inside Foo so that is fine.

However, I don't like the usage of Arb.registerByType here due to global state etc.

How can I rewrite this test so that Arb.registerByType is not necessary?

Ideally, I would design a FsCheck config once and pass that to each test.


Solution

  • Using vanilla FsCheck

    Create the config once like this:

    let config =
        {
            FsCheck.Config.Default with
                Arbitrary = [ typeof<CustomArbitrary> ]
        }
    

    Then use it to check each test like this:

    Check.One(config, roundTrip)
    

    Using FsCheck.Xunit

    If you switch to Properties/Property instead of Fact, you don't even need an explicit config instance or the Check class:

    open FsCheck.Xunit
    
    [<Properties(Arbitrary=[| typeof<CustomArbitrary> |])>]
    module MyTests =
    
        [<Property>]
        let ``Encode.foo Decode.foo round-trip`` (x : Foo) =
            let json =
              x
              |> Encode.foo
              |> Encode.toString 2
    
            let decoded =
              json
              |> Decode.fromString Decode.foo
    
            Ok x = decoded
    
        [<Property>]
        let ``Some other test`` () =
            true
    

    More info on this approach here.

    By the way, be careful about using . characters in your test names, because some test frameworks (e.g. Visual Studio) use them to define a test hierarchy.