How can I ignore the value of a discriminated union case in FsUnit's assert?
Take for example:
type TransactionAttempt = {
Deposited:float
Requires:float
}
type RequestResult =
| Denied of TransactionAttempt
| Granted of Product
In my test, I want to do this:
let display = balance |> select Pepsi
display |> should equal Denied
I wouldn't mind doing this:
display |> should equal (Denied _)
However, I'm forced to do this:
display |> should equal (Denied {Deposited=0.25; Requires=1.00})
Note how explicit I have to be with the expression above.
Hence, I just want to know if it was denied. I don't care about the details.
Here's the actual test:
[<Test>]
let ``Vending machine reflects more money required for selection``() =
// Setup
let balance = Quarter |> insert []
// Test
let display = balance |> select Pepsi
// Verify
display |> should equal (Denied {Deposited=0.25; Requires=1.00})
Here's the function:
let select product balance =
let attempt = { Deposited=balance
Requires=product |> getPrice }
let paidInFull = attempt.Deposited >= attempt.Requires
if not paidInFull then
Denied attempt
else Granted product
Here's the entire domain:
module Machine
type Deposit =
| Nickel
| Dime
| Quarter
| OneDollarBill
| FiveDollarBill
type TransactionAttempt = {
Deposited:float
Requires:float
}
type State =
| OutOfService
| PaymentReceived of Deposit
| WaitingForSelection
| NotPaidInFull of TransactionAttempt
type Product =
| Pepsi
| Coke
| Sprite
| MountainDew
type RequestResult =
| Denied of TransactionAttempt
| Granted of Product
(* Functions *)
open System
let display = function
| OutOfService -> "Out of Service"
| WaitingForSelection -> "Make selection"
| NotPaidInFull attempt -> sprintf "%s Required" ((attempt.Requires - attempt.Deposited).ToString("C2"))
| PaymentReceived deposit -> match deposit with
| Nickel -> "5¢"
| Dime -> "10¢"
| Quarter -> "25¢"
| OneDollarBill -> "$1.00"
| FiveDollarBill -> "$5.00"
let getBalance coins =
coins |> List.fold (fun acc d -> match d with
| Nickel -> acc + 0.05
| Dime -> acc + 0.10
| Quarter -> acc + 0.25
| OneDollarBill -> acc + 1.00
| FiveDollarBill -> acc + 5.00) 0.00
let insert balance coin =
coin::balance |> getBalance
let getPrice = function
| Pepsi -> 1.00
| Coke -> 1.00
| Sprite -> 1.00
| MountainDew -> 1.00
let select product balance =
let attempt = { Deposited=balance
Requires=product |> getPrice }
let paidInFull = attempt.Deposited >= attempt.Requires
if not paidInFull then
Denied attempt
else Granted product
The simplest thing I can come up with is to write a predicate for the check you want:
let isDenied du =
match du with
| Denied _ -> true
| _ -> false
Or since let f x = match x with ...
is equivalent to let f = function ...
, that could be:
let isDenied = function Denied _ -> true | _ -> false
Then your test looks like:
display |> isDenied |> should be True
Note that True
, with a capital T, is a constraint. If you're comparing to the Boolean value, then it would be
display |> isDenied |> should equal true
If you find you're having to write LOTS of these custom predicates, there's probably a more general solution involving writing a custom constraint for NUnit or XUnit or whatever testing framework you're using on the back end. But as a relative newbie to F#, you should probably go with the simple solution first, and generalize it later.
P.S. If you do decide to write a custom constraint, look at https://github.com/fsprojects/FsUnit/blob/master/src/FsUnit.NUnit/FsUnit.fs (if you're using NUnit) or https://github.com/fsprojects/FsUnit/blob/master/src/FsUnit.Xunit/FsUnit.fs (if you're using XUnit) for inspiration.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With