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 during code change.

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 package 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 creating a new file that contains mock implementations of your interface and all the logic needed to create stubs and define mock behavior.

Choosing GoMock – or any mock package – 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 source code changes in isolation while validating interoperability with other applications.

A mocking bird

API Mocking Tools: Our Top 8 Picks

Learn about the benefits, drawbacks, and key use cases for our top 5 API mocking tools: Postman, MockServer, GoMock, MockAPI, and Speedscale

Prerequisites for GoMock

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 mockgen tool into $GOPATH/bin. Mockgen uses code generation to create mock code. However, the mockgen tool isn’t necessary to make GoMock work because you can write mock code 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 code 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: Source 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 this source 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 the mockgen tool to generate your Mock Object by defining the interfaces’ import path and the destination’s import path to where Mock Objects from code generation should live. If no destination is specified, mocks will print to standard output. Although mockgen supports other modes like reflect mode, let’s use source mode to mock from a source file (differences between source mode and reflect mode can be found in the GitHub’s readme).

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

If you look at source mode’s mock implementations in mocks/mocks.go, 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 generated into a destination file or standard output.

$// 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 on differing return values:

$ 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.

Enforcing call order

Since mocked method calls can run in any order, we can enforce varied call order dependencies. The following enforce equivalent call order dependencies:

firstCall := mockObj.EXPECT().SomeMethod(1, "first")

secondCall := mockObj.EXPECT().SomeMethod(2, “second”).After(firstCall)

mockObj.EXPECT().SomeMethod(3, “third”).After(secondCall)

gomock.InOrder(

   mockObj.EXPECT().SomeMethod(1, “first”),

   mockObj.EXPECT().SomeMethod(2, “second”),

   mockObj.EXPECT().SomeMethod(3, “third”),

)

With equivalent call order dependencies, our once varied call order dependencies are now closer to source code.

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.

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 using mockgen for code generation, 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.

Conclusion

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.

Service Mocking with Speedscale

Learn more about Service Mocks

BLOG

Considerations When Mocking APIs in Kubernetes

BLOG

Feature Spotlight: Service Mocks

Ensure performance of your Kubernetes apps at scale

Auto generate load tests, environments, and data with sanitized user traffic—and reduce manual effort by 80%
Start your free 30-day trial today