views:

398

answers:

7

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.

+1  A: 

A simple solution would be to change your

f x = ...

to

f2 g x = ...

and then

f = f2 g
ftest = f2 gtest
Igor Krivokon
It gets more "fun" when g is expected to be able to call f recursively, but that's solvable in both this and the class-based solution.
ephemient
That makes sense. I suppose if I'm just using one function from a service (some logical grouping of functions), then passing the function as a parameter is easiest. But if I'm using several functions, building a class would reduce the number of parameters.
Clint Miller
+3  A: 

Couldn't you just pass a function named g to f? As long as g satisfies the interface typeOfSomeParms -> gReturnType, then you should be able to pass in the real function or a mock function.

eg

f g = do
  ...
  g someParams
  ...

I have not used dependency injection in Java myself, but the texts I have read made it sound a lot like passing higher-order functions, so maybe this will do what you want.


Response to edit: ephemient's answer is better if you need to solve the problem in an enterprisey way, because you define a type containing multiple functions. The prototyping way I propose would just pass a tuple of functions without defining a containing type. But then I hardly ever write type annotations, so refactoring that is not very hard.

Nathan Sanders
Is "enterprisey" supposed to be a compliment? ;)
ephemient
A: 

You could just have your two function implementations with different names, and g would be a variable that is either defined to be one or the other as you need.

g :: typeOfSomeParms -> gReturnType
g = g_mock -- change this to "g_real" when you need to

g_mock someParms = ... -- mock implementation of g

g_real someParms = ... -- real implementation of g
newacct
The downside of this approach is that I have to change my source code to toggle g back and forth between g_mock and g_real every time I run my tests or my real product.
Clint Miller
+1  A: 

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.

ephemient
+4  A: 

Unit testing is for chumps, when you can have Automated Specification-Based Testing. You can generate arbitrary (mock) functions using the Arbitrary type-class provided by QuickCheck (the concept you're looking for is coarbitrary), and have QuickCheck test your function using as many mock functions as you like.

"Dependency Injection" is a degenerate form of implicit parameter passing. In haskell, this is achieved with type-classes that reflect the values of types. See the paper Implicit Configurations.

Apocalisp
That paper looks like it is proposing an extremely verbose and hard to understand type machinery. Applying that to all code to make it testable would render haskell completely unreadable IMO.
+1  A: 

To follow up on the edit asking about multiple functions, one option is to just put them in a record type and pass the record in. Then you can add new ones just by updating the record type. For example:

data FunctionGroup t = FunctionGroup { g :: Int -> Int, h :: t -> Int }

a grp ... = ... g grp someThing ... h grp someThingElse ...

Another option that might be viable in some cases is to use type classes. For example:

class HasFunctionGroup t where
    g :: Int -> t
    h :: t -> Int

a :: HasFunctionGroup t => <some type involving t>
a ... = ... g someThing ... h someThingElse

This only works if you can find a type (or multiple types if you use multi-parameter type classes) that the functions have in common, but in cases where it is appropriate it will give you nice idiomatic Haskell.

Ganesh Sittampalam
+2  A: 

If the functions you depend on are in another module then you could play games with visible module configurations so that either the real module or a mock module is imported.

However I'd like to ask why you feel the need to use mock functions for unit testing anyway. You simply want to demonstrate that the module you are working on does its job. So first prove that your lower level module (the one you want to mock) works, and then build your new module on top of it and demonstrate that works too.

Of course this assumes that you aren't working with monadic values, so it doesn't matter what gets called or with what parameters. In that case you probably need to demonstrate that the right side effects are being invoked at the right time, so monitoring what gets called when is necessary.

Or are you just working to a corporate standard that demands that unit tests only exercise a single module with the whole rest of the system being mocked? That is a very poor way of testing. Far better to build your modules from the bottom up, demonstrating at each level that the modules meet their specs before proceeding to the next level. Quickcheck is your friend here.

Paul Johnson