Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock for testing in Haskell?

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.

like image 243
Clint Miller Avatar asked Jun 11 '09 23:06

Clint Miller


2 Answers

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.

like image 133
Apocalisp Avatar answered Sep 16 '22 13:09

Apocalisp


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.

like image 42
ephemient Avatar answered Sep 20 '22 13:09

ephemient