Question

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).

Was it helpful?

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

OTHER TIPS

It appears FSUnit doesn't (or can't, I'm not sure) directly support this use case.

The next best thing I've found is to declare a TestResult type like the following and use a match to reduce the result to this type.

type TestResult =
| Pass
| Fail of obj

Here is the reducing match

let testResult =
    match result with
    | OptionA(_) -> Pass
    | other -> Fail(other)

Now you can just use should equal to ensure the correct result.

testResult  |> should equal Pass

The benefits of this solution are strong typing but more importantly in the failure case you can see what the invalid result was.

It doesn't look very elegant, but you can extract type from a value of state:

let instanceOfState (state: 'a) =
    instanceOfType<'a>

And then use it in the test:

(increment state) |> should be (instanceOfState <| StateTwo 88)

EDIT

Yes, unfortunately the type is always MyState. Looks like pattern matching or ugly reflection are inevitable.

What if FsUnit already supports an assertion against a specific union case, albeit one restricted to values of the type Microsoft.FSharp.Core.Choice<_,...,_>?

Let's leverage this with a multi-case active pattern, which uses Reflection to check against the union case name.

open System.Reflection
open Microsoft.FSharp.Reflection

let (|Pass|Fail|) name (x : obj) =
    let t = x.GetType()
    if FSharpType.IsUnion t &&
        t.InvokeMember("Is" + name,
            BindingFlags.GetProperty, null, x, null )
        |> unbox then Pass
    else Fail x

Should be working now:

increment state
|> (|Pass|Fail|) "StateTwo"
|> should be (choice 1)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top