Go and Test: Mocking

Mocking with Golang

Go has his own mock library that you can see here, I think that it is not simple and easy to use, but there is another library that allows us to do mocking quickly and easily. The only thing that we have to do is to ensure that we implement the interfaces.

The library is testify. his library has a package to assert, very easy to use, I recommend this for your test, and a mocking package, that I want to show you how to use.

Mock

To use the mock package you only have to import the package

 import "github.com/stretchr/testify/mock"

In your test file you must define a structure for your mock

type mockObject struct{
    mock.Mock
}

Then the structure mockObject could be any interface that you need. Imagine that we have to implement a service reques, this service is represented by the next interface.

type Service interface {
	RequestForInformation(request string) string
}

We must implement a new business logic that must call the service. This logic could be

type Business struct {
	service Service
}

func (b Business) doGoodThings(theThing string) (string, error) {

	result := b.service.RequestForInformation(theThing)
	if len(result) == 0 {
		return "", fmt.Errorf("Bad result")
	}
	return result, nil
}

As we don’t have the service implemented or we are doing TDD and we only want to test the business logic, then we can mock the service layer to test it. We only have to make an instance of our mock and pass it to business structure.

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

type mockObject struct {
	mock.Mock
}

func TestBusinessLogic(t *testing.T) {
	serviceMock := mockObject{}
	b := Business{service: serviceMock}
	result, err := b.doGoodThings("GoodRequest")
	assert.Nil(t, err)
	assert.Equal(t, "OK", result)

}

If we use this code we’ll have an error because mockObject doesn’t implement the interface of our service.

func (m mockObject) RequestForInformation(request string) string {
	args := m.Called(request)
	return args.String(0)
}

Inside of our implementation we must pass the request parameter to mock with Called function, and return the values that the interface needs.

If we execute now the code we get an error that says that it doesn’t know what to do because we don’t tell the mock what to Do. Telling to mock what it has to do is with function On

--- FAIL: TestBusinessLogic (0.00s)
panic: 
assert: mock: I don't know what to return because the method call was unexpected.
	Either do Mock.On("RequestForInformation").Return(...) first, or remove the RequestForInformation() call.
	This method was unexpected:
		RequestForInformation(string)
		0: "GoodRequest"
	at: [mock_test.go:15 mock.go:19 mock_test.go:23] [recovered]
	panic: 
assert: mock: I don't know what to return because the method call was unexpected.
	Either do Mock.On("RequestForInformation").Return(...) first, or remove the RequestForInformation() call.
	This method was unexpected:
		RequestForInformation(string)
		0: "GoodRequest"
	at: [mock_test.go:15 mock.go:19 mock_test.go:23]

goroutine 51 [running]:

Now it is when we say to mock what to do. This happens with On function, we must pass the name of functions and values for the function and then we say with Return method what it has to return.

func TestBusinessLogic(t *testing.T) {
    serviceMock := mockObject{}
    //What the mock to do
    serviceMock.On("RequestForInformation", "GoodRequest").Return("OK")
    
	b := Business{service: serviceMock}
	result, err := b.doGoodThings("GoodRequest")
	assert.Nil(t, err)
	assert.Equal(t, "OK", result)

}

A important thing about mock if that we have to ensure that the method is called, or the number of times that it is called, or if it is called with the correct params, etc. To ensure that it was called in the example above we would do the next code.

func TestBusinessLogic(t *testing.T) {
    serviceMock := mockObject{}
    //What the mock has to do
    serviceMock.On("RequestForInformation", "GoodRequest").Return("OK")
    
	b := Business{service: serviceMock}
	result, err := b.doGoodThings("GoodRequest")
	assert.Nil(t, err)
    assert.Equal(t, "OK", result)
    
   	//Test if the method is called and with specific param
	serviceMock.MethodCalled("RequestForInformation", "GoodRequest")
	//Test if the method was called 1 time
	serviceMock.AssertNumberOfCalls(t, "RequestForInformation", 1)
	//Test if the method don't was called with "BadRequest" param
	serviceMock.AssertNotCalled(t, "RequestForInformation", "BadRequest")
}

The mock allows us to establish how many times a result returns, then the next time it is called return error.

//Return OK only one time
serviceMock.On("RequestForInformation", "GoodRequest").Return("OK").Once()

serviceMock.On("RequestForInformation", "GoodRequest").Return("OK").Times(3)

Another good option is that we can indicate that the service block during X time

serviceMock.On("RequestForInformation", "GoodRequest").Return("OK").After(time.Second * 3)

Of course we can put any number of input param and any of output param in our mock. And any type of param.

//This time the param will be any of the type not specific
serviceMock.On("MethodWithParams", mock.AnythingOfType("string"), mock.AnythingOfType("int"), 
mock.AnythingOfType("model.Event")).
Return(event,fmt.Errorf("Error happen"))
//For custom type we must indicate the package too "model.Event"

//We can return nil for type error
serviceMock.On("MethodWithParams", mock.AnythingOfType("string"), mock.AnythingOfType("int"), mock.AnythingOfType("model.Event")).Return(event,nil)

To defined the mock to example before we must say to the mock that the type to return is “model.Event”

func (mock mockObject) changeEscrow(myString string, myInt int,myEvent model.Event) (model.Event, error) {
    args := mock.Called(change, event, ebHeader)
    //We use Get to custom types
    return args.Get(0).(model.Event), args.Error(1)
}

As a last note I want to say something that I do when I work with this mock to do work easier with the real implementation an with the mock version. I defined a method in which you can pass the interface of the services, but in this case the interface will be nill, then it Will create an instance of a real implementation.

func NewBusiness(serviceImpl Service) Business {
	if serviceImpl == nil {
		serviceImpl = NewService()
	}
	return Business{service: serviceImpl}
}

Then I create the instance in the test with the method. In the case of a no test code, the method only passes nil like param. Of course you could did this for every service that you have, or DAO, or any other interface.

    serviceMock := mockObject{}
    //What the mock has to do
    serviceMock.On("RequestForInformation", "GoodRequest").Return("OK")
    
	b := NewBusiness(serviceMock)

Conclusions

This library allows us a quick and easy way to do mock.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.