Mockery; Mock Everything In Go.

Dec 1, 2019 | Golang

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.

linux ulimits

Learn how to set ulimit on Linux

Often times you will need to increase the maximum number of file descriptors in order to achieve proper functionality of your application. Perhaps you have a database or have run into the "Too many open files" error. In this tutorial, we will go over increasing this...
linux ulimits

Learn how to set ulimit on Linux

Often times you will need to increase the maximum number of file descriptors in order to achieve proper functionality of your application. Perhaps you have a database or have run into the "Too many open files" error. In this tutorial, we will go over increasing this...