Mocking in Golang Using Testify

I have used golang for sometime but only recently I finally understand how to do object mocking in a golang test. Since I came from Java, the way of how mocking an object in golang was not really clear for me. This post is my self documentation of how I reached to my current understanding. In this post, I am using mocking feature from testify. I would assume that reader will be familiar with basic golang to understand this post.

As example, I will create a very simple service like this:

GreeterService is a service that allows you to give a greeting. There are two greeting methods:

  • Greet() will greet you depends on what language you set when creating the service
  • GreetDefaultMessage() will greet you using default message and doesn't care of the language

Internally, Greet() will call db.FetchMessage(lang) and GreetDefaultMessage() will call db.FetchDefaultMessage(). In real world we will imagine, DB class is class that calls a real database. Therefore, we need a mock in our test to avoid our test calling the actual database. There is no class in golang but we can consider struct behaviour to be equivalent with class.

Implementation

Let see the implementation that we will write in a package called service. First we define the package.

package service

Then we will create a db struct and its interface that we name as DB.

type db struct{}

// DB is fake database interface.
type DB interface {
	FetchMessage(lang string) (string, error)
	FetchDefaultMessage() (string, error)
}

After that we will create GreeterService interface and greeter struct that implements the interface which has dependency to the DB interface. greeter struct also receives lang parameter in its 2nd argument's constructor.

type greeter struct {
	database DB
	lang     string
}

// GreeterService is service to greet your friends.
type GreeterService interface {
	Greet() string
	GreetInDefaultMsg() string
}

To make db struct implicity implement DB interface, we will add the required methods and set *db as the receiver.

func (d *db) FetchMessage(lang string) (string, error) {
    // in real life, this code will call an external db
    // but for this sample we will just return the hardcoded example value
	if lang == "en" {
		return "hello", nil
	}
	if lang == "es" {
		return "holla", nil
	}
	return "bzzzz", nil
}

func (d *db) FetchDefaultMessage() (string, error) {
	return "default message", nil
}

Next we will need to implement the actual greeter methods, Greet() and GreetInDefaultMsg().

func (g greeter) Greet() string {
	msg, _ := g.database.FetchMessage(g.lang) // call database to get the message based on the lang
	return "Message is: " + msg
}

func (g greeter) GreetInDefaultMsg() string {
	msg, _ := g.database.FetchDefaultMessage() // call database to get the default message
	return "Message is: " + msg
}

Above, the greeter methods is calling DB to get the actual message.

It will be helpful to create a factory function for the Greeter and DB.

func NewDB() DB {
	return new(db)
}

func NewGreeter(db DB, lang string) GreeterService {
	return greeter{db, lang}
}

Last part of the implementation, we will write a main function to run the service.

package main

import (
	"fmt"
	"testify-mock/service"
)

func main() {
	d := service.NewDB()

	g := service.NewGreeter(d, "en")
	fmt.Println(g.Greet()) // Message is: hello
	fmt.Println(g.GreetInDefaultMsg()) // Message is: default message

	g = service.NewGreeter(d, "es")
	fmt.Println(g.Greet()) // Message is: holla

	g = service.NewGreeter(d, "random")
	fmt.Println(g.Greet()) // Message is: bzzzz
}

The output after running that will be as follow.

$ go run main.go
Message is: hello
Message is: default message
Message is: holla
Message is: bzzzz

Test and Mock

After having the implementation above, we will write write a test and mock DB dependency. As stated in the beginning, we want to prevent calling actual database when running the test.

To achieve that goal, we will mock the DB interface. Unfortunately the way how mock is created in golang is not as straight forward as in java. In java using mockito, mocking can be as simple as this:

GreetingService mock = Mockito.mock(GreetingService.class);

But in golang we need to create a new struct and embed a testify mock object in it like this:

type dbMock struct {
	mock.Mock
}

Then to make that mock object to comply with DB interface, we need to implement all the methods of the interface. Also there is a specific call need to be done inside the implemented method. The first one is we need to call method Mock.Called(args) and passing the arguments if the method we want to mock has some arguments.

In the example below, the call is d.Called(lang). Then return value of that call will be used as return value the method that we want to mock. Both method are returning (string, error). Therefore the return statement of the method are return args.String(0), args.Error(1). The pattern of how we should write the return statement in the mocked methods is, args.<ReturnValueType>(<index>). Index is started from zero.

func (d *dbMock) FetchMessage(lang string) (string, error) {
	args := d.Called(lang)
	return args.String(0), args.Error(1)
}

func (d *dbMock) FetchDefaultMessage() (string, error) {
	args := d.Called()
	return args.String(0), args.Error(1)
}

If we want to mock a method with return signature of (int, string, bool), the return statement we need to write is return args.Int(0), args.String(1), args.Bool(2).

If the return signature is not a simple object, the return should be written using type assertion like return args.Get(0).(*MyObject), args.Get(1).(*AnotherObjectOfMine). See testify godoc for some more extra information.

After creating the mocking object, we can use it in our test.

Mocking Method Without Argument

When we want to mock FetchDefaultMessage() which is method without argument we have to use On(<methodName>, args...) method. The args is variadic therefore we can pass no argument or multiple arguments.

func TestMockMethodWithoutArgs(t *testing.T) {
	theDBMock := dbMock{} // create the mock
	theDBMock.On("FetchDefaultMessage").Return("foofofofof", nil) // mock the expectation
	g := greeter{&theDBMock, "en"} // create greeter object using mocked db
	assert.Equal(t, "Message is: foofofofof", g.GreetInDefaultMsg()) // assert what actual value that will come
	theDBMock.AssertNumberOfCalls(t, "FetchDefaultMessage", 1) // we can assert how many times the mocked method will be called
	theDBMock.AssertExpectations(t) // this method will ensure everything specified with On and Return was in fact called as expected
}

Mocking Method With Argument

Previously we mock FetchDefaultMessage() method which doesn't have any argument. Now let see example if we want to mock a method with an argument.

func TestMockMethodWithArgs(t *testing.T) {
	theDBMock := dbMock{}
	theDBMock.On("FetchMessage", "sg").Return("lah", nil) // if FetchMessage("sg") is called, then return "lah"
	g := greeter{&theDBMock, "sg"}
	assert.Equal(t, "Message is: lah", g.Greet())
	theDBMock.AssertExpectations(t)
}

Mocking Method With Argument That We Don't Care

Sometime we want to mock a method but we don't care the actual argument that is passed. For that purpose we can use mock.Anything in the second parameter onward of On() method argument.

func TestMockMethodWithArgsIgnoreArgs(t *testing.T) {
	theDBMock := dbMock{}
	theDBMock.On("FetchMessage", mock.Anything).Return("lah", nil) // if FetchMessage(...) is called with any argument, please also return lah
	g := greeter{&theDBMock, "in"}
	assert.Equal(t, "Message is: lah", g.Greet())
	theDBMock.AssertCalled(t, "FetchMessage", "in")
	theDBMock.AssertNotCalled(t, "FetchMessage", "ch")
	theDBMock.AssertExpectations(t)
	mock.AssertExpectationsForObjects(t, &theDBMock)
}

Mocking Method With Argument Using MatchedBy

There also will be a time we want to mock a method with a complex argument but want to match the mock based on certain property of the argument or calculation from it. For example, we want to mock FetchMessage method but only if the lang argument is started by letter i.

func TestMatchedBy(t *testing.T) {
	theDBMock := dbMock{}
	theDBMock.On("FetchMessage", mock.MatchedBy(func(lang string) bool { return lang[0] == 'i' })).Return("bzzzz", nil) // all of these call FetchMessage("iii"), FetchMessage("i"), FetchMessage("in") will match 
	g := greeter{&theDBMock, "izz"}
	msg := g.Greet()
	assert.Equal(t, "Message is: bzzzz", msg)
	theDBMock.AssertExpectations(t)
}

Mockery

As we can see in the previous section, before touching actual test and mocking the behaviour, we need to manually create the mock struct. Mockery can help us from the need to do the manual work.

First we just need to install mockery:

go get github.com/vektra/mockery/.../

Then generate the mock.

$ mockery -name <interfaceToMock>

A file contains ready to use mock will be generated and we can add it to our test.

Full source of example code in this post is available in this repo.