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.