Reputation: 85
I want to unit test this bit of code using xUnit in F#. How do I handle the options?
From Scott Wlaschin's book: Domain Modeling Made Functional
type UnitQuantity = private UnitQuantity of int
// ^ private constructor
// define a module with the same name as the type
module UnitQuantity =
/// Define a "smart constructor" for UnitQuantity
/// int -> Result<UnitQuantity,string>
let create qty =
if qty < 1 then
// failure
Error "UnitQuantity can not be negative"
else if qty > 1000 then
// failure
Error "UnitQuantity can not be more than 1000"
else
// success -- construct the return value
Ok (UnitQuantity qty)
Test:
let ``Check UnitQuantity.create is one`` () =
// ARRANGE
let expected = 1
// ACT
//let unitQtyResult = UnitQuantity.create 1
match UnitQuantity.create 1 with
| Error msg -> 0
//printfn "Failure, Message is %s" msg
| Ok x -> 0
// let innerValue = UnitQuantity.value actual
// ASSERT
//Assert.Equal(expected,actual)
I know the ACT
is all wrong, that is where I am hung up. I do not understand F# options nor xUnit.net nor unit testing enough yet to Assert on the actual value from the function.
Upvotes: 7
Views: 1393
Reputation: 1135
Faced with a similar problem, I decided I wasn't so concerned with checking the value itself, but with checking the behavior above and below the threshold. I decided to verify the result is Ok
if the value is in range and an Error
outside the range.
type UnitQuantity = private UnitQuantity of int
module UnitQuantity =
let min = 1
let max = 1000
let create qty =
if qty < min then
Error $"UnitQuantity cannot be less than {min}"
else if qty > max then
Error $"UnitQuantity cannot be more than {max}"
else
Ok (UnitQuantity qty)
module Tests =
let min = 1
let max = 1000
let shouldBeError = function
| Error _ -> ()
| _ -> failwith "is not error"
let shouldBeOk = function
| Ok _ -> ()
| _ -> failwith "is not Ok"
[<Fact>]
let ``create unit qty at min`` () =
UnitQuantity.create min |> shouldBeOk
[<Fact>]
let ``create unit qty below min`` () =
UnitQuantity.create (min - 1) |> shouldBeError
[<Fact>]
let ``create unit qty at max`` () =
UnitQuantity.create max |> shouldBeOk
[<Fact>]
let ``create unit qty above max`` () =
UnitQuantity.create (max + 1) |> shouldBeError
Upvotes: 0
Reputation: 329
A general rule of thumb with unit tests is that the Act section should be a single statement.
Everything that we want to check about the result is some form of assertion
So we want to assert whether the result is either an Ok<UnitQuantity>
or an Error<string>
.
This is where pattern matching allows us to test this very succintly
let ``Check UnitQuantity.create is one`` () =
// ARRANGE
let qty = 1 // The quantity we supply is one
let expected = qty // We expect to get that value back
// ACT
let actual = UnitQuantity.create qty
// ASSERT
// Check if we have an Ok or an Error
match actual with
| Ok unitQuantity ->
// If Ok, check the value is what we expect
let innerValue = UnitQuantity.value unitQuantity
Assert.Equal(innerValue, expected)
// If Error raise an AssertException to fail the test
| Error errorMessage ->
let error = sprintf "Expected Ok, was Error(%s)." errorMessage
Assert.True(false, error) // Force an assertion failure with our error message
Note the UnitQuantity.value
method, it' a simple unwrap function you can add to the end of the UnitQuantity
module that will give you back the int
value so you can easily compare it
let value (UnitQuantity e) = e
If you wanted to test an option
type, it would be very similar, using a match statement like so
match actual with
| Some value ->
// Do your assertions here
()
| None ->
// Do your assertions here
()
Upvotes: 4
Reputation: 26213
I'd probably compare the result directly rather than pattern matching. However, you can't create an Ok
result for Result<UnitQuantity, string>
because of the private
constructor.
You can use the built-in Result.map
to map the Ok
value of a result. Using UnitQuantity.value
you can map from Result<UnitQuantity, string>
to Result<int, string>
. So this should work:
let expected = Ok 1
let actual = UnitQuantity.create 1 |> Result.map UnitQuantity.value
Assert.Equal(expected, actual)
Upvotes: 5