Mockery is an awesome tool that offers the ability to generate mocks from Go interfaces. Additionally, it’s output uses the testify framework, so you can easily plug in your mocks and perform granular tests on your code. When combined with the power of a Makefile, it makes mocking a breeze.
The Makefile
Some people might be turned off by the use of a makefile. However, it is going to make this significantly more convenient, especially as your project grows.
GOC=go GO111MODULE=on DIRECTORIES=$(sort $(dir $(wildcard pkg/*/*/))) MOCKS=$(foreach x, $(DIRECTORIES), mocks/$(x)) .PHONY: all test clean-mocks mocks all: demo demo: $(GOC) build -o demo test: | mocks go test ./... clean-mocks: rm -rf mocks mocks: $(MOCKS) $(MOCKS): mocks/% : % mockery -output=$@ -dir=$^ -all
This makefile takes all of the subdirectories which match the pattern “pkg/*/*” and then matches them with an output to be in the folder mocks/pkg/...
. For each of these pairs, it makes a call to mockery with the -all flag. This flag tells it to mock all of the interfaces in a directory.
The Go Code to Mock
For this example, we will have a Teller struct, which exports a method Tell. This method takes in a story and reads it chapter by chapter to an audience far and wide. Here is what the story interface and Teller struct look like.
type Story interface { ReadChapter(chapter int) ([]byte, error) HasChapter(chapter int) bool }
type Teller struct { } func (teller Teller) say(words []byte) { //Pretend this makes it talk } func (teller Teller) Tell(story Story) error { for i := 0; story.HasChapter(i); i++ { words, err := story.ReadChapter(i) if err != nil { return err } teller.say(words) } return nil }
How can you make sure that the Teller reads every single chapter until there are no more in the story? You mock the story! First, you must generate the mocks with the command make mocks
(or you can just run the test with make test
, it knows to make the calls to mockery first). Here is our tester code for the Teller.
import ( "testing" demoMock "github.com/moduledemos/mockery-demo/mocks/pkg/demo" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) func TestTeller_Tell(t *testing.T) { teller := Teller{} story := new(demoMock.Story) noChapters := 20 story.On("ReadChapter", mock.Anything).Return( []byte(":)"), nil).Times(noChapters) for i := 0; i < noChapters; i++ { story.On("HasChapter", i).Return(true).Once() } story.On("HasChapter", noChapters).Return(false).Once() err := teller.Tell(story) assert.NoError(t, err) story.AssertExpectations(t) }
You can see that we can mock different return values for the HasChapter
function based on the input given to it. Additionally, you might notice .Times(n)
or .Once
appended to the call to .On
. When combined with the call to AssertExpectations
at the end, this enforces the number of times each variation is called.
If it does not matter what the input is, you can use the global mock.Anything
from the testify library to match with any given input. With all of these features, I was able to create a test that ensured that all of the chapters would be read until the function HasChapter returned false. All of this, in just ~12 lines of code. If you like doing more work with less code, then I highly recommend using mockery and testify for all of your Go testing needs.