Table of Contents
- Introduction
- Prerequisites
- Setting Up
- Mocking with Interfaces
- Mocking with External Packages
- Common Errors and Troubleshooting
- 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!