Overview

Get started today
Replay past traffic, gain confidence in optimizations, and elevate performance.

GoMock allows you to simulate dependencies and removes reliance on other teams or systems. In other words, GoMock can generate a response that matches your dependency without using the dependency.

Imagine you’re developing a payment processing application that relies on a third-party payment processing API. You’ve written several tests—unit tests, integration tests, end-to-end tests, etc.—yet you find that the unreliability of the payment processing API is often the cause of test failures.

By mocking the API, i.e. by simulating its responses, you can validate all the functionality of your application without the performance of the real API playing a role. This post covers how to implement GoMock in your application alongside the benefits and limitations of mocks.

What is GoMock and Why Choose It?

GoMock is a testing framework provided by the Golang team. It integrates with the rest of the Go ecosystem, like go:generate and the built-in testing package. GoMock isn’t the only Golang testing framework, but it’s the preferred tool for many because of its powerful type-safe assertions.

As you’ll see in the next section, GoMock works by generating a Mock Object from interfaces, meaning GoMock requires interfaces to function. In practical terms, generating a Mock Object is the creation of a new file that contains an implementation of your interface and all the logic needed to create stubs and define mock behavior.

Choosing GoMock—or any mocking tool—depends on solving dependency issues in testing. While integration tests are valuable, as they test the entire chain of a request, they’re often time-consuming, complex, and/or costly. Additionally, if your mocks are properly configured and match the real dependency, you may find a decreased need to perform full integration tests in the first place.

Essentially, mocks provide unit testing ease and most of the benefits from integration testing. You get to test your code changes in isolation while validating interoperability with other applications.

Prerequisites

Before you dive into the tutorial part of this post, make sure you meet a few prerequisites.

  • A basic understanding of the Go programming language

While not strictly necessary, this tutorial is more useful if you have a good grasp of Go’s structure. If not, start learning at GoByExample.

  • A system with Go installed

This tutorial is written using Go v1.20.1. Ensure you’ve installed this version or a later one.

Installing GoMock

Before working with GoMock, you need to install it. Do this by running:

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

Note that this installs the tool mockgen into $GOPATH/bin. Mockgen.The tool is used to generate Mock Objects. But it isn’t necessary to make GoMock work because you can write Mock Objects without the tool. However, the tool makes the process much easier.

Next, install GoMock in your Go project. First you have to initialize your project as a Go Module:

$ mkdir $GOPATH/src/gomock-getting-started && \
  cd $GOPATH/src/gomock-getting-started && \
  go mod init gomock-getting-started

Start adding any necessary modules to your project, like GoMock:

$ go get github.com/golang/mock/gomock

By now, you’ve installed everything you need, and you can start creating Mock Objects to test your application. To do so in this tutorial, you need to create an application first.

Creating Your First Mock with GoMock

Keeping with the example from the introduction, let’s make a simple program that resembles an API interacting with a payment processor. To do so, create a main.go file that implements an interface and two structs.

NOTE: All code can be found in this GitHub repo.

$ cat <<EOF > main.go
package main

import (
  "net/http"
  "bytes"
  "encoding/json"
  "errors"
)

// Interface defining functions for PaymentProcessor implementation
type PaymentProcessor interface {
  Charge(amount float64, token string) error
}

// Defines the struct for StripePaymentProcessor, which will implement the PaymentProcessor interface
type StripePaymentProcessor struct{}

// Implementation of the Charge function defined in the PaymentProcessor interface
func (s *StripePaymentProcessor) Charge(amount float64, token string) error {

  // Define empty json object for use in HTTP request
 data := map[string]string{}
 json_data, err := json.Marshal(data)

 // execute HTTP request
 resp, err := http.Post("https://api.stripe.com/v1/charges", "", bytes.NewBuffer(json_data))

 // Return error if any exists
 if err != nil {
  return err
 }

 // Decode the response to json
 // Note: this isn't used anywhere, as this function definition only returns an error
 // This is done purely to simplify the example
 var res map[string]interface{}
 json.NewDecoder(resp.Body).Decode(&res)

 return nil
}

// Define the PaymentProcessorClient that is going to be tested
type PaymentProcessorClient struct {
 PaymentProcessor PaymentProcessor
}

// Define the Charge function for the client, which is going to be tested
func (c *PaymentProcessorClient) Charge(amount float64, token string) error {
  // Define the lowest possible charge
  if amount < 20 {
    return errors.New("Charge too low")
  }

  return c.PaymentProcessor.Charge(amount, token)
}
EOF

Understanding the specifics of every single code line isn’t necessary, although you can read the inline comments for more info. But you need to understand that there’s a PaymentProcessor interface that defines a Charge function. Then, the StripePaymentProcessor implements this interface and interacts with the Stripe API. The function body mainly showcases the typical flow of such requests. But you don’t need it in this tutorial because that isn’t what you’ll be mocking.

After this, create a PaymentProcessorClient struct that uses the PaymentProcessor interface, as shown in the Charge function, where it’s passing the function parameters to the interface implementation.

The principle behind this code is that the PaymentProcessorClient can interact with any payment processor. In this case, it’s Stripe, but you can easily swap it because it’s using an interface.

Now that you’ve defined a simple application with an interface, you can generate your first Mock Object.

Creating a Mock Object

After defining your interfaces, use mockgen to generate your Mock Object by defining the file containing your interfaces and the destination of the Mock Object you generate.

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

If you look at the newly-generated mocks/mocks.go file, you’ll see that it’s created a number of structs. You don’t need to understand all the code, but here’s what structs are created:

$// 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"
)

// MockPaymentProcessor is a mock of PaymentProcessor interface.
type MockPaymentProcessor struct {
 ctrl *gomock.Controller
 recorder *MockPaymentProcessorMockRecorder
}

// MockPaymentProcessorMockRecorder is the mock recorder for MockPaymentProcessor.
type MockPaymentProcessorMockRecorder struct {
 mock *MockPaymentProcessor
}

// NewMockPaymentProcessor creates a new mock instance.
func NewMockPaymentProcessor(ctrl *gomock.Controller) *MockPaymentProcessor {
 mock := &MockPaymentProcessor{ctrl: ctrl}
 mock.recorder = &MockPaymentProcessorMockRecorder{mock}
 return mock
}

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

// Charge mocks base method.
func (m *MockPaymentProcessor) Charge(amount float64, token string) error {
 m.ctrl.T.Helper()
 ret := m.ctrl.Call(m, "Charge", amount, token)
 ret0, _ := ret[0].(error)
 return ret0
}

// Charge indicates an expected call of Charge.
func (mr *MockPaymentProcessorMockRecorder) Charge(amount, token interface{}) *gomock.Call {
 mr.mock.ctrl.T.Helper()
 return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Charge", reflect.TypeOf((*MockPaymentProcessor)(nil).Charge), amount, token)
}

MockPaymentProcessor is the main struct that makes GoMock work because it contains the controller and recorder for your tests. This is GoMock’s brain, as you’ll see in the implementation shortly. To view the description of each part of the Mock Object, use the inline comments.

Configuring GoMock

By now, you’re ready to start using mocks inside your tests. Start by creating a main_test.go in your root directory with all the boilerplate code needed:

$ cat <<EOF > main_test.go
package main_testimport (
  "testing"
  "gomock-getting-started"
  "gomock-getting-started/mocks"
  "github.com/golang/mock/gomock"
  "errors"
)

func TestCharge(t *testing.T) {
}
EOF

Note that it’s important to save your project to $GOPATH/src because Go uses the path for local imports. It’s why "gomock-getting-started" and "gomock-getting-started/mocks" works and placing your code anywhere else will result in an error.

With the boilerplate code in place, start building the test. Start the test by defining the mock controller and use it to generate the mock PaymentProcessor and the test PaymentProcessorClient, which you’ll use to test the Charge()function:

func TestCharge(t *testing.T) {
  mockCtrl := gomock.NewController(t)
  mockPaymentProcessor := mock_main.NewMockPaymentProcessor(mockCtrl)
  testPaymentProcessorClient := &main.PaymentProcessorClient{ PaymentProcessor: mockPaymentProcessor }
}

The next step is ensuring that GoMock verifies any expectations you set during mock behavior configuration, like the number of times a given function needs to be called:

...
  testPaymentProcessorClient := &main.PaymentProcessorClient{ PaymentProcessor: mockPaymentProcessor }

  defer mockCtrl.Finish()
}

Define the behavior of the Mock Object before writing the tests for the PaymentProcessorClient’s Charge() function. Although GoMock and mockgen are smart, they can’t analyze your code to generate responses. This is why you should analyze your code as part of your test. Do this using the EXPECT() function:

...
  defer mockCtrl.Finish()
  mockPaymentProcessor.EXPECT().Charge(100.0, "test_token").Return(nil).Times(1)
}

Here, the EXPECT() call is immediately followed by the.Charge() call where you define the parameters the mock implementation of the PaymentProcessor’s Charge()function should accept. Your test will fail if any of the test code makes calls to the Charge() function not matching the parameters you’ve defined.

To avoid hard coding you can replace parameters with gomock.Any(). However, where possible, use specific parameters to make your tests less ambiguous.

The last two functions Return(nil) and Times(1) define that a call to Charge(100.0, "test_token") should return nil. But the call has to be used once during the Mock Object lifetime. Use AnyTimes() to avoid specifics about the number of times you call a function, although this should be avoided when possible to reduce ambiguity. Now that the Mock Object is fully configured, start writing your test.

Writing Tests With the Mock Object

To call the function in your test is like calling it anywhere else in your codebase:

...
  mockPaymentProcessor.EXPECT().Charge(100.0, "test_token").Return(nil).Times(1)

  err := testPaymentProcessorClient.Charge(100.0, "test_token")
}

With the function having been called, validate the return value of the function:

...
err := testPaymentProcessorClient.Charge(100.0, "test_token")
    if err != nil {
            t.Fail()
    }
}

This is a simple test case like other tests you’d write in Go. If there’s an error, the test fails. If not, it passes. If you look at the main.go file, you’ll see that the PaymentProcessor.Charge() function also contained logic that validates the charge amount not being below 20. Let’s write a test for that.

...
    if err != nil {
            t.Fail()
    }

err = testPaymentProcessorClient.Charge(10.0, "test_token")

    if err.Error() != "Charge too low"; {
            t.Errorf("Error returned was: %s", err.Error())
    }
}

Here, you see the same err variable being used as before, but the Charge amount has been changed to 10.0. Then, the code is verifying that the error response matches what’s been defined in the function implementation in main.go. Now, verify that both tests are working as expected by running:

$ go test
PASS
ok      gomock-getting-started  0.003s

The Mock works as expected, and you’ve successfully verified the implementation of Charge() without using the actual Stripe API. A clear indicator is getting no errors, athough the call to Stripe defined in main.go is not a proper implementation.

You can also verify this by modifying the mocks to purposefully fail:

...
// mockPaymentProcessor.EXPECT().Charge(10.0, "test_token").Return(errors.New("Error in Stripe")).AnyTimes()
    mockPaymentProcessor.EXPECT().Charge(30.0, "test_token").Return(errors.New("Error in Stripe")).AnyTimes()
...  
    // err = testPaymentProcessorClient.Charge(10.0, "test_token")
    err = testPaymentProcessorClient.Charge(30.0, "test_token")
...

This time the test should fail:

$ go test
--- FAIL: TestCharge (0.00s)
    main_test.go:44: Error returned was: Error in Stripe
FAIL
exit status 1
FAIL    gomock-getting-started  0.003s

This is a simple example of how to use GoMock in your tests; however, it’s far from a comprehensive overview. The official GoMock README is a great place to get started if you want to learn more.

Limitations of GoMock

As seen in this post, using GoMock isn’t too complex. But consider some of the limitations before integrating it into your infrastructure.

Only works with Go

You’re unlikely to have issues if your organization only uses Go. But many organizations are switching to microservices architecture that use different programming languages. In this case—or cases of uncertainties about the future use of Go—consider using a language-agnostic tool instead.

Sits directly in your codebase

A GoMock living as code directly in your codebase is positive and negative. It allows easy structure and access to writing tests.

But the inability to create a central system for tests is a possible drawback. For example, deciding whether to use a new version of GoMock organization-wide is time-consumning, depending on the number of individual projects.

Requires significant setup

As seen in this post, setting GoMock up for a single project isn’t too complicated, although it’s time-consuming and complex when you have many applications. You can add projects manually or automate the process. But neither option is easy or quick to execute.

Mostly suited for unit testing

Although it’s the core idea behind GoMock, you must know it’s only suited for unit testing, not integration and end-to-end testing. It means implementing other types of testing inherently involves adding other tools.

Only works with interfaces

If you’re generating Mock Objects with mockgen, then you’re limited to mocking interfaces. The upside is that it encourages the use of interfaces, which is arguably a good development paradigm. But it’s a limitation you should consider.

Overall, GoMock is a great tool for unit testing in Go. However, consider using user traffic to build a mock server, which addresses all the limitations above. Irrespective of the type of testing you’re doing, you’ll gain troves of advantages by using mocks.

About The Author

Learn more about this topic