There are numerous articles showing how to unit test in Go, but many of them only cover simple cases for basic unit tests. There is nothing thorough on deeper concepts. In this article, I hope to cover more complex and real-world unit testing examples. We will test an API endpoint that interacts with a database.

Since most of the existing articles on Go unit testing focus on just a simple example, there is no mocking, no stubbing, no complex data types, no interfaces. It was really painful for me to get to a point where I could write useful unit tests for my code. My goal is to try to help other beginners to save some time by giving my experience and insights.

What we will learn in this article

We will test a REST API handler function which simply binds the input request payload and stores the data into a MySQL database. I will be using the Gin framework, but the results can be applied to any API library.

First, we will look at some tightly coupled (incorrect) code that I initially wrote.

Let’s get an overview of what the code does.

The NewAdmin function takes an argument of type *gin.Context which contains all the details related to the request and response.

The ShouldBindJSON binds the request with the AdminDetailsStruct object and fills the details which are part of the request. If there is any mismatch with struct details, it will return an error.

Then we form a query and Exec will execute the query. connection.SQLConn is of type *mysql.Db .

Now that you’ve got a brief overview of what the code does, we now discuss how to actually test.

Mistakes in the above code

There are some mistakes in the initial code that I wrote which make unit testing difficult:

The function parameter is tightly coupled to a specific type

Mocking a function of an external package ( connection.SQLConn.Exec ). A lot of articles suggest monkey patching and interfaces. I am personally against monkey patching, and working with interfaces requires the developer to be really good at writing testable code.

Refactoring and building out the app

So the first thing is to refactor the code to be loosely coupled. The structure of the refactored project will be as below.

connection -> main.go This will open up a connection and initialize once during the server startup.

interfaces -> main.go Defines an interface for *gin.Context . There are many methods for *gin.Context but for our use case these two will be good enough

create -> createNewAdmin.go

create -> dependencies.go

create -> structs.go

Don’t worry about the code, we will discuss line by line in a moment.

If you observe the difference, we made two changes:

Instead of accepting *gin.Context as function parameter, I’ve changed it to an interface containing JSON and ShouldBindJSON methods. So any struct that implements these two functions can be passed as a function parameter.

as function parameter, I’ve changed it to an interface containing and methods. So any struct that implements these two functions can be passed as a function parameter. We’ve assigned connection.SQLConn.Exec to a variable. The reason why we did this is that it gives us the flexibility to change the variable at runtime by some other function. Any function that’s changing this variable has to assign a function with same fn parameter types and return values.

Steps for writing unit tests

Step 1: Create a struct that implements JSON and ShouldBindJSON methods ( ginContext is the struct).

and methods ( is the struct). Step 2: Create a variable of type ginContext and pass it as a parameter to NewAdmin . Whenever JSON or ShouldBindJSON function triggers, instead of calling Gin’s methods, it will call the methods of the struct that is passed by the test function.

and pass it as a parameter to . Whenever or function triggers, instead of calling Gin’s methods, it will call the methods of the struct that is passed by the test function. Step 3: Assign the Exec variable which holds connection.SQLConn.Exec to a dummy variable and assign a mock function to Exec . So whenever Exec() is called instead of calling actual MySQL Exec() , it calls test file Exec() .

variable which holds to a dummy variable and assign a mock function to . So whenever is called instead of calling actual MySQL , it calls test file . Step 4: On execution completion of the test function, assign Exec to its initial value. This ensures that other tests won’t be affected by this change.

Note: Never run tests in parallel when you do stuff like this. That can cause some nasty effects due to race conditions.

The code written in ShouldBindJSON binds dummy data. You don’t need to bother about with right now.

Finally, we need to test the amount of code covered by our tests.

go test -coverprofile=c.out && go tool cover -html=c.out

Just run this command and you should see the coverage report in the default browser of your system

This is my very first article on Medium, so any suggestions or improvements are gladly accepted.