Suppose I am defining a Haskell function f (either pure or an action) and somewhere within f I call function g. For example:
f = ... g someParms ...
How do I replace function g with a mock version for unit testing?
If I were working in Java, g would be a method on class SomeServiceImpl
that implements interface SomeService
. Then, I'd use dependency injection to tell f to either use SomeServiceImpl
or MockSomeServiceImpl
. I'm not sure how to do this in Haskell.
Is the best way to do it to introduce a type class SomeService:
class SomeService a where g :: a -> typeOfSomeParms -> gReturnType data SomeServiceImpl = SomeServiceImpl data MockSomeServiceImpl = MockSomeServiceImpl instance SomeService SomeServiceImpl where g _ someParms = ... -- real implementation of g instance SomeService MockSomeServiceImpl where g _ someParms = ... -- mock implementation of g
Then, redefine f as follows:
f someService ... = ... g someService someParms ...
It seems like this would work, but I'm just learning Haskell and wondering if this is the best way to do this? More generally, I like the idea of dependency injection not just for mocking, but also to make code more customizable and reusable. Generally, I like the idea of not being locked into a single implementation for any of the services that a piece of code uses. Would it be considered a good idea to use the above trick extensively in code to get the benefits of dependency injection?
EDIT:
Let's take this one step further. Suppose I have a series of functions a, b, c, d, e, and f in a module that all need to be able to reference functions g, h, i, and j from a different module. And suppose I want to be able to mock functions g, h, i, and j. I could clearly pass the 4 functions in as parameters to a-f, but that's a bit of a pain to add the 4 parameters to all the functions. Plus, if I ever needed to change the implementation of any of a-f to call yet another method, I'd need to change its signature, which could create a nasty refactoring exercise.
Any tricks to making this type of situation work easily? For example, in Java, I could construct an object with all of its external services. The constructor would store the services off in member variables. Then, any of the methods could access those services via the member variables. So, as methods are added to services, none of the method signatures change. And if new services are needed, only the constructor method signature changes.
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.
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