Test with Testify and Mockery in Go
- Authors
- Name
- Amit Shekhar
- Published on
In Go, we can write unit tests using two packages: Testify, a toolkit with common assertions and mocks, and Mockery, which auto-generates the mocks for our Go interfaces.
In this blog, we will learn about writing the unit test with Testify and Mockery in Go (Golang) project.
I am Amit Shekhar, Founder @ Outcome School, I have taught and mentored many developers, and their efforts landed them high-paying tech jobs, helped many tech companies in solving their unique problems, and created many open-source libraries being used by top companies. I am passionate about sharing knowledge through open-source, blogs, and videos.
I teach AI and Machine Learning, and Android at Outcome School.
Join Outcome School and get a high-paying tech job:
Let's get started.
These two packages are very useful during the testing of any Go files.
- testify: A toolkit with common assertions and mocks that plays nicely with the standard library.
- mockery: A mock code autogenerator for Golang used in testing.
As we know that the testify package is used for the purpose of the assertions.
mockery package provides the ability to easily generate mocks for Golang interfaces using the testify package. It removes the boilerplate coding required to use mocks.
So, let's start learning to use both packages to write tests in Go.
I will be using the below-mentioned project for the implementation part. The project follows a clean architecture in Go Language. You can find the complete code for unit testing using the Testify and Mockery mentioned in the blog in the project itself.
Link to the project: Go Backend Clean Architecture.
In the project, we have Controller, Usecase, and Repository. In this blog, we will learn to write the test by taking the Usecase as an example.
Consider the TaskUsecase present inside the given project.
package usecase
import (
"context"
"time"
"github.com/amitshekhariitbhu/go-backend-clean-architecture/domain"
)
type taskUsecase struct {
taskRepository domain.TaskRepository
contextTimeout time.Duration
}
func NewTaskUsecase(taskRepository domain.TaskRepository, timeout time.Duration) domain.TaskUsecase {
return &taskUsecase{
taskRepository: taskRepository,
contextTimeout: timeout,
}
}
func (tu *taskUsecase) Create(c context.Context, task *domain.Task) error {
ctx, cancel := context.WithTimeout(c, tu.contextTimeout)
defer cancel()
return tu.taskRepository.Create(ctx, task)
}
func (tu *taskUsecase) FetchByUserID(c context.Context, userID string) ([]domain.Task, error) {
ctx, cancel := context.WithTimeout(c, tu.contextTimeout)
defer cancel()
return tu.taskRepository.FetchByUserID(ctx, userID)
}
So, our aim is to test the Usecase that is dependent on Repository. We will have to mock the Repository.
As the mockery generates the mocks for Golang interfaces, suppose we have the following interface for the TaskRepository.
type TaskRepository interface {
Create(c context.Context, task *Task) error
FetchByUserID(c context.Context, userID string) ([]Task, error)
}
As a next step, we need to run the following command to generate the mock for this repository.
mockery --dir=domain --output=domain/mocks --outpkg=mocks --all
Note: You will have to modify the command based on your project structure and directory.
It should generate the TaskRepository mock that we will be using for the testing.
A quick note for you
No matter which tech domain you work in, get familiar with these topics:
- LLM
- RAG
- MCP
- Agent
- Fine-tuning
- Quantization
We put it all together in one video:
AI Engineering Explained: LLM, RAG, MCP, Agent, Fine-Tuning, and Quantization
No need to stop reading - bookmark it and watch later when you get time. Future you will thank you.
Now, let's get back to the topic.
Now, that mock for the Repository is also ready, we can write the test for the Usecase.
We will write the test for the FetchByUserID method present in the Usecase.
Here, we are testing two cases:
- Success: The repository returns data successfully without any errors.
- Error: The repository returns an error.
The following is the code showing how to write those tests using the testify and mockery:
package usecase_test
import (
"context"
"errors"
"testing"
"time"
"github.com/amitshekhariitbhu/go-backend-clean-architecture/domain"
"github.com/amitshekhariitbhu/go-backend-clean-architecture/domain/mocks"
"github.com/amitshekhariitbhu/go-backend-clean-architecture/usecase"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func TestFetchByUserID(t *testing.T) {
mockTaskRepository := new(mocks.TaskRepository)
userObjectID := primitive.NewObjectID()
userID := userObjectID.Hex()
t.Run("success", func(t *testing.T) {
mockTask := domain.Task{
ID: primitive.NewObjectID(),
Title: "Test Title",
UserID: userObjectID,
}
mockListTask := make([]domain.Task, 0)
mockListTask = append(mockListTask, mockTask)
mockTaskRepository.On("FetchByUserID", mock.Anything, userID).Return(mockListTask, nil).Once()
u := usecase.NewTaskUsecase(mockTaskRepository, time.Second*2)
list, err := u.FetchByUserID(context.Background(), userID)
assert.NoError(t, err)
assert.NotNil(t, list)
assert.Len(t, list, len(mockListTask))
mockTaskRepository.AssertExpectations(t)
})
t.Run("error", func(t *testing.T) {
mockTaskRepository.On("FetchByUserID", mock.Anything, userID).Return(nil, errors.New("Unexpected")).Once()
u := usecase.NewTaskUsecase(mockTaskRepository, time.Second*2)
list, err := u.FetchByUserID(context.Background(), userID)
assert.Error(t, err)
assert.Nil(t, list)
mockTaskRepository.AssertExpectations(t)
})
}
Case 1: Success
In this case, we have mocked the method of Repository to return success with the data.
Then, we are passing the mocked Repository into the Usecase.
After that, we are calling the method of the Usecase.
And then finally, asserting to check whether everything is as expected or not.
Case 2: Error
In this case, we have mocked the method of Repository to return an error.
Then, we are passing the mocked Repository into the Usecase.
After that, we are calling the method of the Usecase.
And then finally, asserting to check whether everything is as expected or not.
Now, our test is ready to be run. We can run the test and check.
We can run all the tests with the following command:
go test ./...
This is how we can write the unit test by using the Testify and Mockery package in Go (Golang) project.
I will highly recommend going through the project and checking the tests written for the Controller, Usecase, and Repository.
Link to the project: Go Backend Clean Architecture.
Frequently Asked Questions
What is the difference between Testify and Mockery?
Testify is a toolkit with common assertions and mocks that plays nicely with the Go standard library. Mockery is a mock code autogenerator for Go. We use Testify to write the assertions, and we use Mockery to generate the mocks for our interfaces so we do not have to write the mock boilerplate by hand.
Does Mockery depend on Testify?
Yes. Mockery generates mocks for Go interfaces using the Testify package. It removes the boilerplate coding required to use mocks.
Why do we mock the repository when testing a usecase?
We mock the repository because the usecase depends on it. By mocking the repository, we can control what it returns, such as data for the success case or an error for the error case. Then, we pass the mocked repository into the usecase and assert that everything is as expected.
How do we run all the tests in a Go project?
We can run all the tests in a Go project with the go test ./... command. This runs the tests we have written, such as the success and error tests for the usecase, and lets us check whether everything works as expected.
That's it for now.
Thanks
Amit Shekhar
Founder @ Outcome School
You can connect with me on:
Follow Outcome School on:
Read all of our high-quality blogs here.
Subscribe to our newsletter to get our latest AI and Machine Learning blogs straight to your inbox.
