Testing and mocking are essential practices in software development that help ensure code reliability, stability, and correctness. The Go programming language has built-in support for testing, making it easier for developers to write unit tests for their code.

However, testing can be challenging when dealing with external dependencies such as databases, APIs, and other services. That’s where mocking comes in. By simulating these dependencies, developers can test their code in isolation and avoid relying on external services during testing.

In this article, we’ll explore the different mocking methods available in Go. We’ll also dive into the GoMock framework, a powerful tool for generating mock objects in Go.

By the end of this article, you’ll have a solid understanding of the different mocking methods in Go and how to choose the right one for your needs.

Mocking techniques in Go

There are several Go mocking techniques can be used to create mock objects. Let’s look at each of these techniques and see how they work.

Manually creating mock objects

Manually creating mock objects involves creating a struct that implements the same interface as the real object, but with mock data and behavior. This technique is useful when dealing with simple objects that have a small number of methods. For example:

package main

import "errors"

type Database interface {
   Get(key string) (string, error)
   Set(key, value string) error
}

type MockDatabase struct {
   Data map[string]string
}

func (db *MockDatabase) Get(key string) (string, error) {
   value, ok := db.Data[key]
   if !ok {
       return "", errors.New("key not found")
   }
   return value, nil
}

func (db *MockDatabase) Set(key, value string) error {
   db.Data[key] = value
   return nil
}

In the example above, we defined a Database interface with two methods: Get and Set. We then created a MockDatabase struct that implements the same interface, but with a Data field that stores mock data.

The Get method returns the value associated with the given key from the Data field, and the Set method adds a key-value pair to the Data field.

Implementing interfaces

Another way to create mock objects is by implementing interfaces directly. This technique is best used when dealing with more complex objects with many methods. See the following example:

package main

type Database interface {
   Get(key string) (string, error)
   Set(key, value string) error
}

type MockDatabase struct {
   GetFunc func(key string) (string, error)
   SetFunc func(key, value string) error
}

func (db *MockDatabase) Get(key string) (string, error) {
   return db.GetFunc(key)
}

func (db *MockDatabase) Set(key, value string) error {
   return db.SetFunc(key, value)
}

Using function callbacks

The final basic technique for mocking in Go involves using function callbacks. This technique is useful when dealing with functions that are not part of an interface, like so:

package main

import (
   "testing"

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

func ProcessData(data string, f func(string) string) string {
   result := f(data)
   // do some processing with result
   return result
}

func TestProcessData(t *testing.T) {
   mockFunc := func(data string) string {
       return "mocked result"
   }
   result := ProcessData("input data", mockFunc)
   assert.Equal(t, "mocked result", result)
}

The example above defines a ProcessData function that takes in a string and a callback function. The function uses the callback to transform the input data and returns the result.

In our test case, we defined a mock function that always returns a fixed result and passes it into the ProcessData function. We then used an assertion to ensure that the result returned by ProcessData matches the expected result.

Using the GoMock framework for mock generation

When writing unit tests for code that depends on external dependencies, such as databases or web services, it can be difficult to ensure that the tests are repeatable and predictable.

One approach to solving this problem is to use mock objects, which simulate the behavior of the external dependencies but with controlled inputs and outputs. Using mocking frameworks like GoMock or Testify Mock can help you create mock objects to use in your tests.

The next sections will explain how to install and use GoMock to create and manage mock objects for unit testing in Go.

Key features of GoMock

GoMock provides several features that make it a powerful tool for writing advanced unit tests in Go. For example, it allows you to define interfaces that represent the behavior you want to mock, automatically generating mock objects based on those interfaces.

GoMock also provides tools for recording the calls made to mock objects, making it easier to verify that your code is behaving as expected.

Additionally, you can use GoMock to set expectations for mock object behavior, such as how often a method should be called and with what arguments.

Installing the GoMock framework

To start using GoMock in your Go project, you will first need to install it. GoMock can be installed using the go get command in your terminal:

go get github.com/golang/mock/gomock

This command will download and install the GoMock package and its dependencies into your Go workspace.

Once GoMock is installed, you can import it into your Go code using the following import statement:

import (
    "github.com/golang/mock/gomock"
)

Install GoMock command line application

GoMock works as a command line application to generate mocked source code, but you also have to install it in your application to use the generated mock code.

Installing the cli tool:

$ go install github.com/golang/mock/mockgen@v1.6.0

To generate mock implementation of specific code, run the following code in the project root, example:

$ mockgen -source=your_code.go -destination=mocks/your_code.go

Creating a mock object using GoMock

To create a mock object using GoMock, you must first define the interface representing the behavior you want to mock. This interface should contain the method signatures that you want to mock.

For example, let’s say we have a Fetcher interface that retrieves data from an external API. We want to mock this behavior in our unit tests. Here’s what the Fetcher interface might look like:

type Fetcher interface {
    FetchData() ([]byte, error)
}

To generate the mock implementation of the Fetcher interface:

$ mockgen -source=gomock_interface.go -destination=mocks/gomock_interface.go

To create a mock object for this interface using GoMock, we need to follow these steps:

  1. Create a new controller using gomock.NewController()
  2. Create a mock object using the controller’s CreateMock method, passing in the interface you want to mock
  3. Set expectations on the mock object’s behavior using GoMock’s methods
  4. Call the controller’s Finish method to indicate you are done with the mock object

Here’s an example code snippet that illustrates how to create a mock object for our Fetcher interface using GoMock:

import (
    "testing"

    "github.com/golang/mock/gomock"
)

func TestFetchData(t *testing.T) {
    // Create a new controller
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()

    // Create a mock object for the Fetcher interface
    mockFetcher := NewMockFetcher(ctrl)

    // Set expectations on the mock object's behavior
    mockFetcher.EXPECT().FetchData().Return([]byte("data"), nil)

    // Call the code under test
    data, err := myFunc(mockFetcher)

    // Assert the results
    if err != nil {
        t.Errorf("Unexpected error: %v", err)
    }
    if !bytes.Equal(data, []byte("data")) {
        t.Errorf("Unexpected data: %v", data)
    }
}

Recording calls to a mock object

GoMock allows you to record the calls made to a mock object and set expectations on the behavior of those calls. This can be useful for verifying that your code is interacting with the mock object correctly.

To record the calls made to a mock object using GoMock, you need to create a new gomock.Call object for each method call you want to record. You can then use the Do method of the gomock.Call object to specify the behavior that should occur when the method is called.

Here’s an example code snippet that illustrates how to record calls to a mock object using GoMock:

import (
    "testing"

    "github.com/golang/mock/gomock"
)

func TestMyFunction(t *testing.T) {
    // Create a new controller
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()

    // Create a mock object for the MyInterface interface
    mockObj := NewMockMyInterface(ctrl)

    // Record the expected calls to the mock object
    call1 := mockObj.EXPECT().MyMethod1()
    call2 := mockObj.EXPECT().MyMethod2("arg1", "arg2")

    // Set expectations on the behavior of the calls
    call1.Return(nil)
    call2.Return("result", nil)

    // Call the code under test
    myFunction(mockObj)

    // Verify that the expected calls were made
    if err := mockObj.AssertExpectationsWereMet(); err != nil {
        t.Errorf("Unexpected error: %v", err)
    }
}

In this example, we create a mock object for the MyInterface interface and then use the EXPECT method to record the expected calls to the mock object. We then use the Return method to specify the behavior of each call.

Finally, we call the code under test and use the AssertExpectationsWereMet method to verify that the expected calls were made. An error would be returned if any expected calls were not made, in which case you could follow best practices for error handling in Go to rectify it.

Mocking interfaces in Go with GoMock

In this part, we are going to learn more details on how to use GoMock to generate mock implementations of interfaces in Go.

Generating mock code

Let’s say we have the following code in your main.go file

package main

// 👇 an interface acting as API Client
type ApiClient interface {
	GetData() string
}

// 👇 a function using the ApiClient interface
func Process(client ApiClient) int {
	data := client.GetData()
	return len(data)
}

func main() {

}

In the above piece of code we have an interface ApiClient that is being used by the Process function. Lets say we want to test the Process function and we require a mock implementation of ApiClient when doing so.

To generate mock implementation of ApiClient run the following code in the project root:

$ mockgen -source=main.go -destination=mocks/main.go

This will generate a mock implementation of the interfaace ApiClient and write the code into mocks/main.go file.

We don’t really care about the generated code but here is the generated code to satisfy your curiosity (don’t bother to understand it, just glance over it).

// Code generated by MockGen. DO NOT EDIT.
// Source: main.go

// Package mock_main is a generated GoMock package.
package mock_main

import (
	reflect "reflect"

	gomock "github.com/golang/mock/gomock"
)

// MockApiClient is a mock of ApiClient interface.
type MockApiClient struct {
	ctrl     *gomock.Controller
	recorder *MockApiClientMockRecorder
}

// MockApiClientMockRecorder is the mock recorder for MockApiClient.
type MockApiClientMockRecorder struct {
	mock *MockApiClient
}

// NewMockApiClient creates a new mock instance.
func NewMockApiClient(ctrl *gomock.Controller) *MockApiClient {
	mock := &MockApiClient{ctrl: ctrl}
	mock.recorder = &MockApiClientMockRecorder{mock}
	return mock
}

// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockApiClient) EXPECT() *MockApiClientMockRecorder {
	return m.recorder
}

// GetData mocks base method.
func (m *MockApiClient) GetData() string {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "GetData")
	ret0, _ := ret[0].(string)
	return ret0
}

// GetData indicates an expected call of GetData.
func (mr *MockApiClientMockRecorder) GetData() *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetData", reflect.TypeOf((*MockApiClient)(nil).GetData))
}

Look closely we have an struct called MockApiClient that has all the functions of ApiClient (GetData in this case). Additionally we have a function called NewMockApiClient that we can use to get an instance of MockApiClient in our tests.

Using the mock code

Create a new file called mock_test.go and add the following code:

package main

import (
	"github.com/golang/mock/gomock"
	mock_main "github.com/gurleensethi/playground/mocks"
	"testing"
)

func TestProcess(t *testing.T) {
	ctrl := gomock.NewController(t)
    
    // 👇 create new mock client
	mockApiClient := mock_main.NewMockApiClient(ctrl)
    
    // 👇 configure our mock `GetData` function to return mock data
	mockApiClient.EXPECT().GetData().Return("Hello World")

	length := Process(mockApiClient)

	if length != 11 {
		t.Fatalf("want: %d, got: %d\n", 11, length)
	}
}

We use the NewMockApiClient function to get an instance that we can use wherever ApiClient interface is expected.

mockApiClient := mock_main.NewMockApiClient(ctrl)

Pay attention to how we configure the GetData function on the mock client to return the data that we want.

mockApiClient.EXPECT().GetData().Return("Hello World")

Check out GoMock documentation it has a lot more options to configure mocks, it has a reflection mode which allows you to generate mocks of interfaces from packages that you have not written yourself and much more.

Conclusion

Go provides various options for mocking in unit tests. You can choose from built-in techniques such as implementing interfaces, more advanced options like the monkey patching approach, or third-party libraries like GoMock or Testify Mock.

Each method has advantages and disadvantages; the choice ultimately depends on your use case, specific needs, and preferences.

For example, GoMock and Testify Mock provide more advanced mocking features like call recording, argument matching, and verification, making them suitable for more complex test cases. However, they also have a steeper learning curve and require additional setup and configuration.

In comparison, as mentioned earlier, manually creating a mock object is useful for simple objects with a small number of methods. However, implementing interfaces directly can be more appropriate for more complex objects with many methods, while function callbacks are best for functions that are not part of an interface.

Regardless of the method you choose, learning to use mocking methods effectively can significantly improve the reliability and maintainability of your unit tests, leading to more robust and stable code.

The complete code for the article is available in this GitHub repo.

Thank your for reading this article 🙏🏻 hopefully you learned something new.

Leave a Reply

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