Mocking dependencies in Go
Overview
We all use a lot of packages when constructing our applications, both internally developed and third party packages. This blog post will show how you can easily mock these packages using mockery
and stretchr/testify/mock.
Let’s dive into it; An example
When writing unit tests for our code we might find the external packages problematic. Consider this case:
import "github.com/mediocregopher/radix.v2/redis"type Handler struct {
db *redis.Client
}func (h *Handler) Ping() (string, error) {
res := h.db.Cmd("INCR", "ping:count")
if res.Err != nil {
return "", res.Err
} return "pong", nil
}
There is a Handler
which is holding the Redis client. Then there is a Ping
method on the Handler
which is using the Redis client. Now, when testing the Ping
method we’d like to check a couple of things:
- When the database is and is not throwing an error
- The response text in positive and negative cases
This would require us to have an actual Redis database running to make the Cmd
call. However for the purpose of testing Ping
we’re less interested in testing the actual database. Our concern is the Ping
method itself and how it behaves under certain circumstances (some of which maybe very hard to reproduce).
The way out; Creating an interface
The observant reader noticed that the db
is a *redis.Client
. Which means that it’s a pointer to a struct. Mocking structs is hard, since we cannot replace them with our own implementation. So let’s refactor our code to use an interface instead.
import "github.com/mediocregopher/radix.v2/redis"type storager interface {
Cmd(string, ...interface{}) *redis.Resp
}type Handler struct {
db storager
}func (h *Handler) Ping() (string, error) {
res := h.db.Cmd("INCR", "ping:count")
if res.Err != nil {
return "", res.Err
} return "pong", nil
}
We have now created a small interface
called storager
which defines the behavior — what methods should be present and what they return — of the database.
Let’s next look at how to test this:
import (
"errors"
"testing""github.com/mediocregopher/radix.v2/redis"
"github.com/stretchr/testify/assert"
)func TestPing(t *testing.T) {
sampleErr := errors.New("sample error") tests := map[string]struct {
storageErr error
response string
err error
}{
"successful": {
storageErr: nil,
response: "pong",
err: nil,
},
"with db error": {
storageErr: sampleErr,
response: "",
err: sampleErr,
},
} for name, test := range tests {
t.Logf("Running test case: %s", name) storage := &mockStorager{}
storage.
On("Cmd", "INCR", []interface{}{"ping:count"}).
Return(&redis.Resp{
Err: test.storageErr,
}).
Once() h := &Handler{
db: storage,
}
response, err := h.Ping()
assert.Equal(t, test.err, err)
assert.Equal(t, test.response, response) storage.AssertExpectations(t)
}
}
Let’s look at this step by step.
- First I’m setting up two test cases in my tabled based test. One for testing when the database is not returning an error. And one for when it does.
- I define
storage
which is of the type*mockStorager
. This is important. I’ll get to why soon. Notice also how I’m setting input parameters and the return value for theCmd
. - Then I validate the response with the test definition.
storage.AssertExpectations(t)
asserts that the expectations fined on thestore
has been full filled. In this case that would be that theCmd("INCR", "ping:count")
was called once and only once.
Now, the *mockStorager
is a struct I have generated using mockery (mockery is a tool for autogenerating mock, if you aren’t using it; please read up on it!). It returns a ready to use mock based on any given interface
. The following command was used in this case:
mockery -name storager -inpkg .
Now I’m able to make mocked tests of the Ping
method, without having to write a mocked database on my own.
The generated mocked interface looks like below. The code is using testify/mock
to implement the actual mock. mockery
is only used to generate the code.
import mock "github.com/stretchr/testify/mock"
import redis "github.com/mediocregopher/radix.v2/redis"// mockStorager is an autogenerated mock type for the storager type
type mockStorager struct {
mock.Mock
}// Cmd provides a mock function with given fields: _a0, _a1
func (_m *mockStorager) Cmd(_a0 string, _a1 ...interface{}) *redis.Resp {
ret := _m.Called(_a0, _a1) var r0 *redis.Resp
if rf, ok := ret.Get(0).(func(string, ...interface{}) *redis.Resp); ok {
r0 = rf(_a0, _a1...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*redis.Resp)
}
} return r0
}var _ storager = (*mockStorager)(nil)
More on testify/mock
There are a number of cool things you can do with this mock
object. In the above test we only used the mock.On(...).Return(...).Once()
. But you could also define how many times you’d expect a certain call to be made.
More details about stretchr/testify/mock
can be found in the package documentation.
Conclusion
There are two main takeaways from this blog post that I would like to highlight:
- Use
interface
s to inject your dependencies. That way testing will become much smoother. - Take help from great community provided software like
mockery
andstretchr/testify/mock
to ease the burden and stay focused on your core business.