Search code examples
f#nunitfsunit

How to have useful assertion-failure messages for FsUnit?


I'm using FsUnit 2.3.2 and I'm not happy with the failure messages. See the examples below:

[<Test>]
let ``test 1``() =
    [1; 3]
    |> should equal [1;2]

... gives me the not-so-helpful message:

Expected and actual are both Microsoft.FSharp.Collections.FSharpList`1[System.Int32]

at FsUnit.TopLevelOperators.should[a,a](FSharpFunc`2 f, a x, Object y) in d:\GitHub\FsUnit\src\FsUnit.NUnit\FsUnit.fs:line 44 at Program.test 1() in F:\work\playground\fsunit\fsunit\Program.fs:line 9

A workaround that I found was to use arrays instead of lists:

[<Test>]
let ``test 2``() =
    [|1; 4|]
    |> should equal [|1;2|]

...produces

Expected and actual are both System.Int32[2]
Values differ at index [1]
Expected: 2
But was: 4

A second problem is if I have an ADT defined

type MyT = 
    A of int 
    | B of string

[<Test>]
let ``test 4``() =
    A 10
    |> should equal (B "abc")

...gives me the message:

Expected: Program+MyT+B
But was: Program+MyT+A

...which I can workaround by implementing ToString for MyT like this:

override this.ToString() = match this with
    | A i -> sprintf "A(%d)" i
    | B s -> sprintf "B(%s)" s

...which will lead to a good message:

Expected: B(abc)
But was: A(10)

...but I would like fsunit to just render MyT values the way (sprintf "%A") does.

Anyway, having to do these workarounds is NOT OK.

How can I obtain useful messages for F# lists without using arrays?

How to obtain useful messages for ADTs?

Is there a good fix for the above issues or should I just drop FsUnit?

Do you have a better recommendation for a unit testing library for F# that doesn't have these issues?


Solution

  • A couple of contenders:

    Expecto

    [<Tests>]
    let tests =
      testList "test group" [
        testCase "strings" <| fun _ ->
            let subject = "Hello World"
            Expect.equal subject "Hello world"
                        "The strings should be equal"
    
        testCase "lists" <| fun _ ->
            let expected = [1; 2]   
            Expect.equal expected [1; 3]
                        "The lists should be equal"
    
        testCase "DUs" <| fun _ ->
            let expected = A 10   
            Expect.equal expected (B "abc")
        ]
    

    Output

    [19:29:46 INF] EXPECTO? Running tests...
    [19:29:46 ERR] test group/strings failed in 00:00:00. 
    The strings should be equal.
              Expected string to equal:
              "Hello world"
                     ↑
              The string differs at index 6.
              "Hello World"
                     ↑
              String does not match at position 6. Expected char: 'w', but got 'W'.
    
    [19:29:46 ERR] test group/lists failed in 00:00:00. 
    The lists should be equal. Actual value was [1; 2] but had expected it to be [1; 3].
    
    [19:29:46 ERR] test group/DUs failed in 00:00:00. 
    The DUs should be equal. Actual value was A 10 but had expected it to be B "abc".
    
    [19:29:46 INF] EXPECTO! 3 tests run in 00:00:00.0028417 – 0 passed, 0 ignored, 3 failed, 0 errored. ( ರ Ĺ̯ ರೃ )
    val it : int = 1
    

    Unquote

    [<Test>]
    let ``The strings should be equal`` () =
        let subject = "Hello World"
        subject =! "Hello world"
    
    Result Message:   
    "Hello World" = "Hello world"
    false
    
    [<Test>]
    let ``The lists should be equal`` () =
        let expected = [1; 2]
        expected =! [1; 3]
    
    Result Message:   
    [1; 2] = [1; 3]
    false
    
    [<Test>]
    let ``The DUs should be equal`` () =
        let expected = A 10
        expected =! (B "abc")
    
    Result Message:   
    A 10 = B "abc"
    false
    

    Unquote's benefit lies in it's Quotations, allowing step-by-step failure messages.

    [<Test>]
    let ``The arrays should be equal`` () =
        let expected = [|0 ; 2 ; 3 ; 4|]
        test <@ (Array.map ((+) 1) [|0 .. 3|]) = expected @>
    
    Result Message:   
    Array.map ((+) 1) [|0..3|] = [|0; 2; 3; 4|]
    Array.map ((+) 1) [|0; 1; 2; 3|] = [|0; 2; 3; 4|]
    [|1; 2; 3; 4|] = [|0; 2; 3; 4|]
    false