Home > Software engineering >  Testing functions returning option<T>
Testing functions returning option<T>

Time:12-30

Consider unit-testing a module defining strings of constrained length as a type for representing user names:

module UserName

type T = UserName of string
let create (userName : string) =
    if userName.Length >= 6 && userName.Length <= 16 
    then Some (UserName userName)
    else None
let apply f (UserName userName) = f userName
let value userName = apply id userName

A unit test to ensure that the function returns None for an invalid input looks simple:

[<Fact>]
let ``UserName must have at least six characters`` () =
    UserName.create "aaa" |> should equal None

However, a unit test for the case when the function returns Some appears to require an extra line for completeness of the match expression:

[<Fact>]
let ``Valid UserName`` () =
    match UserName.create "validname" with
    | Some result ->
        UserName.value result |> should equal "validname"
    | None -> Assert.True(false)

This does not look right to me, because my tests must define code for testing the "unhappy" path that must yield a failure anyway.

I wish I could write this instead

[<Fact>]
let ``Valid UserName`` () =
    UserName.create "validname" |> should equal (Some (UserName "validname"))

but it does not compile (The value or constructor 'UserName' is not defined).

Is there a way to write a unit test of a function returning option<T> that does not require an explicit check of the "unhappy" path (e.g. | None -> Assert.True(false))? I am open to adding more types and/or functions to the UserName module to make it more testable.

CodePudding user response:

Apply value to the inside of Some and then compare:

UserName.create "validname" 
  |> Option.map UserName.value
  |> should equal (Some "validname")
  • Related