Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 384
Mark Pattison Avatar asked Sep 25 '13 09:09

Mark Pattison


People also ask

How is a discriminated union defined?

A discriminated union is a union data structure that holds various objects, with one of the objects identified directly by a discriminant. The discriminant is the first item to be serialized or deserialized. A discriminated union includes both a discriminant and a component.

When to use discriminated union?

Discriminated unions are useful for heterogeneous data; data that can have special cases, including valid and error cases; data that varies in type from one instance to another; and as an alternative for small object hierarchies.

What is FsUnit?

FsUnit is a set of libraries that makes unit-testing with F# more enjoyable. It adds a special syntax to your favorite . NET testing framework. FsUnit currently supports NUnit, xUnit, and MsTest.

What is a union in f#?

Advertisements. Unions, or discriminated unions allows you to build up complex data structures representing well-defined set of choices. For example, you need to build an implementation of a choice variable, which has two values yes and no. Using the Unions tool, you can design this.


2 Answers

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
like image 195
pad Avatar answered Oct 24 '22 16:10

pad


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.

like image 35
Daniel Little Avatar answered Oct 24 '22 17:10

Daniel Little