How to Mock in Go Unit Tests

Table of Contents

  1. Introduction
  2. Prerequisites
  3. Setting Up
  4. Mocking with Interfaces
  5. Mocking with External Packages
  6. Common Errors and Troubleshooting
  7. Summary

Introduction

In Go unit tests, mocking is a technique used to simulate dependencies and control the behavior of external dependencies during testing. Mocking allows you to isolate the code under test and focus on specific scenarios without relying on the actual implementation of external dependencies. This tutorial will guide you through the process of mocking in Go unit tests, using both interfaces and external packages.

By the end of this tutorial, you will be able to:

  • Understand the concept of mocking in Go unit tests
  • Implement mocking using interfaces
  • Mock external dependencies using external packages
  • Handle common errors and troubleshoot mocking issues

Prerequisites

To follow along with this tutorial, you should have basic knowledge of Go programming language and familiarity with writing unit tests. You will need Go installed on your machine, along with a code editor of your choice.

Setting Up

Before we dive into mocking, let’s set up a simple Go project for our examples. We will be creating a package called mathutil that performs various mathematical operations. In the mathutil package, we will have a file called calculator.go which contains a struct Calculator and an interface Adder. The Calculator struct has a function Add that takes two integers and returns their sum. The Adder interface also includes the Add method.

Create a new directory for the project and navigate into it:

mkdir mathutil
cd mathutil

Create the calculator.go file with the following code:

package mathutil

type Calculator struct{}

func (c Calculator) Add(a, b int) int {
    return a + b
}

type Adder interface {
    Add(a, b int) int
}

Great! We have our initial project structure set up. Now let’s move on to mocking in Go unit tests.

Mocking with Interfaces

Mocking with interfaces is a common approach in Go unit tests. It involves creating a mock implementation of an interface that behaves in a specific way for testing purposes. Let’s create a mock implementation of the Adder interface.

Create a new file called mock_test.go in the mathutil package with the following code:

package mathutil

type MockAdder struct {
    AddFunc func(a, b int) int
}

func (m MockAdder) Add(a, b int) int {
    if m.AddFunc != nil {
        return m.AddFunc(a, b)
    }
    return 0
}

In the MockAdder struct, we define a function AddFunc that takes two integers and returns an integer. This function will allow us to customize the behavior of the mock implementation during testing. The Add method checks if AddFunc is set and calls it with the provided arguments. If AddFunc is not set, it returns 0.

Now, let’s create a unit test for our Calculator struct where we will use the mock implementation of Adder.

Create a new file called calculator_test.go in the mathutil package with the following code:

package mathutil

import "testing"

func TestCalculator_Add(t *testing.T) {
    calculator := Calculator{}
    mockAdder := MockAdder{
        AddFunc: func(a, b int) int {
            return a - b
        },
    }

    calculator.Adder = mockAdder

    result := calculator.Add(5, 3)
    if result != 2 {
        t.Errorf("Expected result to be 2, got %d", result)
    }
}

In the test function, we create an instance of Calculator and a MockAdder. We set the AddFunc of the MockAdder to subtract the provided arguments instead of adding them. Then, we assign the mockAdder instance to the Adder field of the Calculator.

Finally, we call the Add method of Calculator with arguments 5 and 3 and assert that the result is 2 (5 - 3).

To run the test, execute the following command in the terminal:

go test

If everything is set up correctly, you should see the test pass.

Congratulations! You have successfully implemented mocking using interfaces in Go unit tests. Now let’s move on to mocking external packages.

Mocking with External Packages

Sometimes, you may need to mock external packages in your unit tests. This can be useful when testing code that relies on external services, databases, or APIs. Go provides a handy package called httptest that allows you to mock HTTP requests and responses. Let’s see how to use it.

First, let’s create a new file called httpclient.go in the mathutil package with the following code:

package mathutil

import (
    "net/http"
    "io/ioutil"
)

type HTTPClient interface {
    Do(req *http.Request) (*http.Response, error)
}

type RealHTTPClient struct{}

func (c RealHTTPClient) Do(req *http.Request) (*http.Response, error) {
    client := http.Client{}
    return client.Do(req)
}

type MyAPI struct {
    Client HTTPClient
}

func (a MyAPI) GetSomeData() (string, error) {
    req, _ := http.NewRequest("GET", "https://api.example.com/data", nil)
    resp, err := a.Client.Do(req)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    return string(body), nil
}

In the mathutil package, we have a RealHTTPClient struct that implements the HTTPClient interface. The Do method of RealHTTPClient performs an actual HTTP request using the standard http.Client. We also have a MyAPI struct that has a Client field of type HTTPClient.

The GetSomeData method of MyAPI sends an HTTP GET request to https://api.example.com/data using the Client and returns the response body as a string.

To mock the external HTTP requests, we need to create a mock implementation of the HTTPClient interface. Let’s create a new file called httpclient_test.go in the mathutil package with the following code:

package mathutil

import (
    "testing"
    "net/http"
    "net/http/httptest"
)

type MockHTTPClient struct {
    DoFunc func(req *http.Request) (*http.Response, error)
}

func (m MockHTTPClient) Do(req *http.Request) (*http.Response, error) {
    if m.DoFunc != nil {
        return m.DoFunc(req)
    }

    return &http.Response{
        StatusCode: http.StatusOK,
        Body:       ioutil.NopCloser(strings.NewReader("Mocked response")),
    }, nil
}

func TestMyAPI_GetSomeData(t *testing.T) {
    api := MyAPI{
        Client: MockHTTPClient{
            DoFunc: func(req *http.Request) (*http.Response, error) {
                return &http.Response{
                    StatusCode: http.StatusOK,
                    Body:       ioutil.NopCloser(strings.NewReader("Mocked response")),
                }, nil
            },
        },
    }

    result, err := api.GetSomeData()
    if err != nil {
        t.Errorf("Expected no error, got %v", err)
    }
    if result != "Mocked response" {
        t.Errorf("Expected result to be 'Mocked response', got %s", result)
    }
}

In the test function, we create an instance of MyAPI and assign it a MockHTTPClient as the Client. We set the DoFunc of MockHTTPClient to return a mocked response with a status code of 200 and body “Mocked response”.

Finally, we call the GetSomeData method of MyAPI and assert that the result is “Mocked response” and there are no errors.

To run the test, execute the following command in the terminal:

go test

If everything is set up correctly, you should see the test pass.

Well done! You have now learned how to mock external packages in Go unit tests using the httptest package.

Common Errors and Troubleshooting

Error: “undefined: MockAdder”

If you encounter an error like “undefined: MockAdder” when running the tests, make sure that the test file calculator_test.go is in the same package as the code being tested (mathutil).

Error: “import cycle not allowed”

Go does not allow importing a package that has an import statement referring back to the current package. If you encounter an error like “import cycle not allowed” when setting up mocks, try reorganizing your code or creating separate test-specific packages.

Error: “no test files”

If you encounter an error like “no test files” when running the tests, make sure that the test file names end with _test.go and are located in the same directory as the code being tested.

Summary

In this tutorial, you have learned how to mock in Go unit tests using interfaces and external packages. Mocking allows you to simulate dependencies and control their behavior during testing, enabling you to focus on specific scenarios without relying on the actual implementation.

You started by setting up a simple Go project and creating a package called mathutil. Then, you learned how to mock interfaces using the provided MockAdder struct. You created a unit test for the Calculator struct, where you used the mock implementation of Adder to customize the behavior of the test.

Next, you explored mocking external packages by creating a mock implementation of the HTTPClient interface. You used the httptest package to simulate HTTP requests and responses and created a test for the GetSomeData method of MyAPI.

Finally, you discovered some common errors and troubleshooting tips, which will help you overcome potential issues while working with mocking in Go unit tests.

Mocking is a powerful technique that significantly improves testability and code quality. By using mocks effectively, you can create comprehensive and reliable unit tests for your Go applications.

Keep learning, practicing, and exploring the world of Go testing, and happy coding!