5
\$\begingroup\$
  1. Is pattern matching within a unit test a code smell?

  2. Do I really need to throw an exception just for my test to fail if the function under test receives invalid arguments (i.e. not Succeeded case value)?

Here's my test:

[<Test>]
let ``get available positions for black soldier`` () =

    // Setup
    let piece = Black ( BlackSoldier , (1,5) )

    // Test
    let available = startGame () |> getPositions
                                 |> optionsFor piece
    // Verify
    match available with
    | OptionsResponse.Succeeded coordinates ->
        let northWestAvailable = coordinates |> List.exists (fun pos -> pos = (0,4))
        let northEastAvailable = coordinates |> List.exists (fun pos -> pos = (2,4))

        (northWestAvailable && northEastAvailable) |> should equal true

    | _ -> failwith "Failed to get options"

Here's the function under test:

let optionsFor piece positions =

    let duplicatesFound = positions |> Seq.countBy id
                                    |> Seq.map snd
                                    |> Seq.exists (fun count -> count > 1)
    if duplicatesFound
    then OptionsResponse.DuplicatesNotAllowed positions
    else match piece |> isKing with
         | false -> let options = positions |> availablePositions 
                                            |> List.filter (optionsForSoldier piece)
                    OptionsResponse.Succeeded options

         | true  -> let options = positions |> availablePositions 
                                            |> List.filter (optionsForKing piece)
                    OptionsResponse.Succeeded options

Appendix:

type Coordinate = int * int

type Space =
    | Occupied  of Piece
    | Available of Coordinate

type OptionsResponse =
    | DuplicatesNotAllowed of Space list
    | Succeeded            of Coordinate list
\$\endgroup\$
1
\$\begingroup\$

Is pattern matching within a unit test a code smell?

I don't think so. When a tested method returns a discriminated union, then using pattern matching in the unit test is natural.

Do I really need to throw an exception just for my test to fail if the function under test receives invalid arguments (i.e. not Succeeded case value)?

Yes, I think you should. The reason for that is that if the function incorrectly returns something other than Succeeded, your test needs to fail. And throwing an exception is how you do that.


Also, if you're a fan of terseness, you can write the List.exists predicate as e.g. ((=) (0,4)), instead of using fun:

let northWestAvailable = coordinates |> List.exists ((=) (0,4))

Or, even better, use should contain:

coordinates |> should contain (0,4)

This is shorter, clearer and will also lead to clearer test failures.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.