The goal of this document is to make a case for using Go interfaces for:
dependency injection (DI)
Easily mocking interfaces
Augmenting an existing instance of an interface
1. Original Motivation - Dependency Injection (DI)
Dependency Injection is a Software Development technique whereby one object supplies the dependencies of another object. This is in contrast to the object building or finding it’s dependencies from some global scope.
Lets look at a simple example. We have a person that would like to introduce himself.
We actually want to support different kinds of people, e.g a loud person, a normal person and perhaps a mute person.
In this case it might be a good idea us to allow Person to have a Say function injected into it, rather then having one tightly coupled implementation.
2. Mocking via DI
A common issue when testing software is dealing with external services that our code depends on. Having to depend on the availability of these services can complicate tests significantly. A few examples are disk resources, network resources, databases, API libraries and more.
Let’s examine how to solve this issue by mocking a service and providing the mocked version via dependency injection. In this way, the code receiving the injected dependency doesn’t even know that it did not receive the real service.
Example - http.Client
For our use-case we will examine http.Client which is a struct from the net/http standard Go lib which allows us to make network requests. The way we make a network request is by building an http.Request first and then providing it to the client to perform.
http.Client does not satisfy any specific interface out of the box, but that doesn’t mean we can’t create an interface to match it.
Now we’ll see a pattern that can be used to very generically mock an interface.
MockHTTPClient takes in a generic DoFn, so we can create many different mock clients.
Notice there are lots of options for mocking HTTPClient: FromStatusCode, FromCookies, FromHeaders, etc. We can also just create a MockHTTPClient on the fly with some special custom logic.
Let’s say we have a function FetchSomeNetworkResources, which accepts an HTTPClient. Although, under normal circumstances we would give it an http.Client, during tests we can just give it one of our mock clients.
We can follow the same pattern to mock any interface.
3. Enhancing existing interfaces
Another cool aspect of this pattern is that it can be used for much more then just mocking. Note: It’s been debated that in the context of “enhancing/augmenting” StubXXX is more appropriate then MockXXX. That said, we will keep using MockXXX for the sake of demonstration.
Example - RetryHTTPClient
Example - RewriteHostHTTPClient
Example - Publisher and friends
Let’s define a Publisher interface.
And a generic mock publisher.
go golang testing mocking design patterns composability