Search code examples
f#discriminated-unionfsunit

How to check the case of a discriminated union with FsUnit?


I'd like to check that a value is of a particular case of a discriminated union, without having to also check any included data. My motivation is to only test one thing with each unit test.

An example is as follows (the last two lines give compilation errors):

module MyState

open NUnit.Framework
open FsUnit

type MyState =
    | StateOne of int
    | StateTwo of int

let increment state =
    match state with
    | StateOne n when n = 10 -> StateTwo 0
    | StateOne n -> StateOne (n + 1)
    | StateTwo n -> StateTwo (n + 1)

[<Test>]
let ``incrementing StateOne 10 produces a StateTwo`` ()=
    let state = StateOne 10
    (increment state) |> should equal (StateTwo 0)             // works fine
    (increment state) |> should equal (StateTwo _)             // I would like to write this...
    (increment state) |> should be instanceOfType<StateTwo>    // ...or this

Can this be done in FsUnit?

I'm aware of this answer but would prefer not to have to write matching functions for each case (in my real code there are far more than two).


Solution

  • If you don't mind using reflections, the isUnionCase function from this answer could be handy:

    increment state 
    |> isUnionCase <@ StateTwo @>
    |> should equal true
    

    Note that it's a bit verbose because you need a function call before comparing values.

    A similar but lighter approach could be comparison of tags:

    // Copy from https://stackoverflow.com/a/3365084
    let getTag (a:'a) = 
      let (uc,_) = Microsoft.FSharp.Reflection.FSharpValue.GetUnionFields(a, typeof<'a>)
      uc.Name
    
    increment state 
    |> getTag
    |> should equal "StateTwo"
    

    Beware that this is not type-safe and you can easily misspell a union case name.

    What I would do is to create a similar DUs for comparison purpose:

    type MyStateCase =
        | StateOneCase
        | StateTwoCase
    
    let categorize = function
        | StateOne _ -> StateOneCase
        | StateTwo _ -> StateTwoCase
    

    In this way, you define categorize once and use it multiple times.

    increment state
    |> categorize
    |> should equal StateTwoCase