Testing REST API in Go with Testify and Mockery

Agus Richard
Nerd For Tech
Published in
7 min readJun 7, 2021
Photo by Mikołaj on Unsplash

“The bitterness of poor quality remains long after the sweetness of low price is forgotten.” — Benjamin Franklin

Let’s start this article with a quote, what do you have in mind when you read this quote?

For me, as the title suggests, it’s related to software testing.

Sometimes when we write some code, the focus is just to create a working code as soon as possible, without any concerns about readibilty, clarity, validity, and maintainability. By forgetting these concerns, probably we’ll have a functional code. Our code is working for some period of time until there are some needs to change or other developers step in. Since our code is unmaintainable in the first place, any future changes will be hard to do. Then, if other developers step in and need to change or extend some functionalities within our code, but our code is extremely dirty, then I will guarantee you that that developer will be mad at you for such dirty work.

This disaster will remain for some time until there is a total change or the application just breaks. One of many ways we can do to prevent this disaster is by writing unit tests.

Just like Uncle Bob said in Clean Code, “Test code is just as important as production code”. Our test code requires thought, design, and care. It must be kept as clean as production code. If our production code can be kept clean, the same thing must be applied to the test code. To do that, it would be good for us to learn unit test in Go using Testify and Mockery.

Introduction

By author

In this article, we will write unit tests for an application following this architecture. There are three main components, Handler, Usecase and Repository. Each of them has its own tasks, Handler has a responsibility to handle incoming and outcoming data, Usecase is for processing the data before and after making contact with the database, and Repository is a layer we use to communicate with the database. To put this simply, Handler is dependent on Usecase and Usecase is dependent on Repository to work. Therefore, because we want to write unit tests instead of integration tests, then we need mocking and we will learn that here.

Disclaimer: This article will cover concepts but overlook some details for an easier read. You can see the full code here https://github.com/agusrichard/go-workbook/tree/master/restapi-test-app.

There are three tools I used quite often in Testify, they are Assert, Suite, and Mock. Let’s start with Assert.

Assert

Assert would be the smallest tool we can use for unit testing. If you have some experience using Go built-in package testing, most of the time, we have to write if statement to assert whether our actual output is correct. But with a help of testify, we can assert the actual output directly without any need to write if statement.

The general pattern to use assert is like this:

func TestSomething(t *testing.T) {
// define expected and actual
assert.Equal(t, expected, actual, "some message about this test")
}

Or if you don’t want to put t into assert each time you use it, you can write the test this way.

func TestSomething(t *testing.T) {
// define expected and actual
assert := assert.New(t)
assert.Equal(expected, actual, "some message about this test")
}

Note that, there are other assertions available, such as Error, False, NotEqual, NoError, Nil, etc. You can get the whole list in the official documentation https://pkg.go.dev/github.com/stretchr/testify/assert.

Suite

Most of the times, we need some set-up and tear-down functionalities when running tests. For example, when we need to establish a connection before running all test cases and need to clear all injected data in a database to start a new session, for this purpose testify gives us Suite. With Suite, we can group related test cases, do some set-up prior to all tests or a single test and do some tear-down after all tests or a single test.

The general pattern to use Suite would be like this.

type MySuite struct {
suite.Suite
}

func (m *MySuite) SetupSuite() {
//... do some setup
// this function runs only once before all tests in a suite
}

func (m *MySuite) TearDownSuite() {
//... do some teardown
// this function runs only once after all tests in a suite
}

func (m *MySuite) SetupTest() {
//... do some setup
// this function runs before each test
}

func (m *MySuite) TearDownTest() {
//... do some teardown
// this function runs after each test
}
func (m *MySuite) TestOne() {
// we can write our test here
}

func TestMySuite(t *testing.T) {
/// we still need this to run all tests in our suite
suite.Run(t, new(MySuite))
}

Mock

The third one is Mock. There is a time when we want to test our functions in isolation. But in order for our function to work, we need some external dependencies. For example, in some function, there is an API call to an external resource, what if we don’t want to make the API call when testing our function. So, how we can test our function in isolation without dependent on other external dependencies? That’s when we need Mock.

This is the example usage:

// main.gotype MyStruct struct {
// some properties if needed
}

type MyStructInterface struct {
DoAmazingStuff(input int) (bool, error)
}
func (m *MyStruct) DoAmazingStuff(input int) (bool, error) {
// do something amazing in here
}
func AmazingFunction(m MyStructInterface) {
m.DoAmazingStuff(123)
}

// main_test.go
type MyStructMocked struct {
mock.Mock
}

func (m *MyStructMocked) DoAmazingStuff(input int) (bool, error) {
// this is a mocked process
args := m.Called(input)

// return the values based on the specified types
return args.Bool(0), args.Error(1)
}
func TestAmazingFunction(t *testing.T) {
// create a mocked instance
testObj := new(MyStructMocked)

// DoAmazingStuff will be called when AmazingFunction runs
// therefore you need to specify the arguments (the second
// position of method On) and its return values
testObj.On("DoAmazingStuff", 123).Return(true, nil)

AmazingFunction(testObj)
testObj.AssertExpectations(t)
}

Here, we defined MyStruct. This is the dependency we need to use AmazingFunction, since we have to pass an instance of MyStruct to it. Then we defined the mocked version of MyStruct which is MyStructMocked. Notice that to use AmazingFunction we need to pass a struct that implements MyStructInterface, so if we want to pass an instance of MyStructMocked to AmazingFunction, MyStructMocked needs to implement the interface. Luckily in Go, to implement an interface we don’t need to specify explicitly since in Go interface is implemented implicitly rather than explicitly. What we have to do is just to create the same method with the same signature as the interface specified.

Real Usage for The App

Okay, previously we’ve learned the basic usage of Testify. Now, how we can use it in real life?

Let’s start with the Repository. In this case, the Repository needs a database connection if it wants to get, insert, update, or delete records. We won’t mock the database connection, we will use a real database. For simplicity, I will give you an example for the create transaction (and overlooking some details). You can look for the full code here https://github.com/agusrichard/go-workbook/tree/master/restapi-test-app.

Repository

The main file for Repository
The test file for Repository

Just like I’ve explained in the previous section, SetupSuite is used when we want to do some set-ups before any tests run in our suite and TearDownTest is used if we want to do something after each test, for example, clean-up any tables we’ve used.

Usecase

For the Usecase, since we need the Repository for it to be working properly but we don’t want to actually use Repository and call the database. It means we need to mock the Repository. Now, Mockery will give us a good help for doing this mocking stuff.

Remember that in the Repository we have an interface for all operations used by it? Now, we need the mocked version and by typing the command mockery --name=TweetRepository. This command will generate an mocked version of our Repository. The result would be something like this.

After this, we can call the mocked version in the Usecase.

The main file for Usecase
The test file for Usecase

Handler

The Handler is somewhat the same as the Usecase because in the Usecase we mocked the Repository, then for the Handler, it means that we need to mock the Usecase. To generate the mocked version for the Usecase, you have to do the same thing as the previous section. The difference is the value given to the name is the Usecase interface. (mockery --name=TweetUsecase). This command will generate the mocked version, then it is ready to be used by our Handler.

The main file for Handler
The test file for Handler

That’s it, we’ve tested all (kinda 😄) the building blocks of our app as an independent unit. Even though the model architecture I use is composed of Handler, Usecase, and Repository. I believe the same concept can be applied to more general cases.

Conclusion

Testify and Mockery would be the handy tools we need if we want to make our test simpler. So learning how to use them, would certainly be a good point for us. As a recap, we can use Assert to validate whether our function does the right thing or not, there are many assertions you can use provided by Testify. We use Suite when we want to group some tests, do some set-up and tear-down. Lastly, we use Mock when our function is dependent on other dependencies but we don’t want to actually use those dependencies.

Thank you for reading, you can look for the full code here https://github.com/agusrichard/go-workbook/tree/master/restapi-test-app.

Feel free to share and give some claps if this article is helpful to you 😁.

--

--

Agus Richard
Nerd For Tech

Software Engineer | Data Science Enthusiast | Photographer | Fiction Writer | Freediver LinkedIn: https://www.linkedin.com/in/agus-richard/