How to mock for testing in Haskell?
Why use unit testing when you can have Automated Specification-Based Testing? The QuickCheck library does this for you. It can generate arbitrary (mock) functions and data using the Arbitrary
type-class.
"Dependency Injection" is a degenerate form of implicit parameter passing. In Haskell, you can use Reader
, or Free
to achieve the same thing in a more Haskelly way.
Another alternative:
{-# LANGUAGE FlexibleContexts, RankNTypes #-}
import Control.Monad.RWS
data (Monad m) => ServiceImplementation m = ServiceImplementation
{ serviceHello :: m ()
, serviceGetLine :: m String
, servicePutLine :: String -> m ()
}
serviceHelloBase :: (Monad m) => ServiceImplementation m -> m ()
serviceHelloBase impl = do
name <- serviceGetLine impl
servicePutLine impl $ "Hello, " ++ name
realImpl :: ServiceImplementation IO
realImpl = ServiceImplementation
{ serviceHello = serviceHelloBase realImpl
, serviceGetLine = getLine
, servicePutLine = putStrLn
}
mockImpl :: (Monad m, MonadReader String m, MonadWriter String m) =>
ServiceImplementation m
mockImpl = ServiceImplementation
{ serviceHello = serviceHelloBase mockImpl
, serviceGetLine = ask
, servicePutLine = tell
}
main = serviceHello realImpl
test = case runRWS (serviceHello mockImpl) "Dave" () of
(_, _, "Hello, Dave") -> True; _ -> False
This is actually one of the many ways to create OO-styled code in Haskell.