# Unit testing and alternative to mocking in Haskell

Let's say you have a simple pure function that applies a discount of 30% if the total price of a list of Items is over 30.00 (let's not delve into the fact that I'm using a Float to indicate the price):

promoGet30PercentOff :: [Item] -> Float
promoGet30PercentOff xs
| total > 30.0 = total * 0.7
| otherwise    = total
where total = sum $map itemPrice xs  and Item and itemPrice are defined as follows: data Item = Apple | Banana deriving Show itemPrice :: Item -> Float itemPrice x = case x of Apple -> 5.9 Banana -> 3.0  I'd now like to test promoGet30PercentOff at the boundary. As I come from Python, my natural reaction would be to mock Item and create some that are priced at 30.0, 30.1 and 0.0, for instance. I believe, this is not possible in Haskell (but correct me if I'm wrong). How would you go about it then? ## 1 Answer You can untangle your promo function from the Item data type promoGet30PercentOffWith :: (a -> Float) -> [a] -> Float promoGet30PercentOffWith price xs | total > 30.0 = total * 0.7 | otherwise = total where total = sum$ map price xs

promoGet30PercentOff :: [Item] -> Float
promoGet30PercentOff = promoGet30PercentOffWith itemPrice


Because promoGet30PercentOffWith is polymorphic, there is a free theorem that all functions of its type satisfy:

promoGet30PercentOffWith f xs = promoGet30PercentOffWith id (map f xs)


This means that the general behavior of that function is entirely determined by its specialized behavior when the first argument is id---so the list argument has type [Float]. Thus, mocking items with artificial prices is as simple as giving the list of prices to the function.

shouldPromo :: [Float] -> IO ()
shouldPromo xs = promoGet30PercentOffWith id xs === sum xs * 0.7

shouldNotPromo :: [Float] -> IO ()
shouldNotPromo xs = promoGet30PercentOffWith id xs === sum xs

(===) :: Eq a => a -> a -> IO ()
x === y = if x == y then return () else error "assertion failed"

test :: IO ()
test = do
shouldPromo [30.1]
shouldNotPromo [30.0]
shouldNotPromo