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