Understanding Unit and Integration Testing in Golang.
By: Victor Steven
Tags: Testing, Interfaces, Unit Testing, Integration, Golang

Testing each functionality in an application can not be overemphasized. In this article, we will explore just two types of software testing: Unit and Integration Testing in a Golang Application.

Introduction

Unit Testing

A unit is the smallest testable part of any software. Unit testing is performed to validate that each unit of an application performs as expected. If an external dependency(a method or a function) is called in the unit being tested, such function/method must be mocked, so as to test only the unit of interest. An example of a unit test: testing a function that saves a user details in the database; for we to achieve unit testing here, we need to mock the database interaction and test only the “save” function to assert that it behaves as expected.

Unit testing increases confidence in changing/maintaining a codebase, it makes debugging easy, makes your code more reliable(softwaretestingfundamentals.com).

Integration Testing

Integration tests verify that different modules or services used by your application work well together. For example, it can be testing the interaction with the database or making sure that microservices work together as expected(atlassian.com).

Functional Testing

Functional tests focus on the business requirements of an application. They only verify the output of action and do not check the intermediate states of the system when performing that action.

There is sometimes confusion between integration tests and functional tests as they both require multiple components to interact with each other. The difference is that an integration test may simply verify that you can query the database while a functional test would expect to get a specific value from the database as defined by the product requirements(atlassian.com).

This article will focus on majorly on Unit testing and Integration testing.

I wrote a messaging Crud Restful API to demonstrate how it works.

Let’s Begin

Get the code on github

Step 1: Basic Setup

Go module is used in this project for dependency management. So you can create the root directory from anywhere on your computer.

Create a directory called “efficient-api”

mkdir efficient-api

Change to the directory

cd efficient-api

and initialize the go modules

go mod init efficient-api

Since we are using a real database in this api, we will need to create a .env file to contain our database secret variables.

touch .env

The database driver used in this article is MySQL.

At this point, we have the following structure:

efficient-api

├── .env

└── go.mod

You can define your database schema like this:

You can install the packages we will be using in this API if you don't already have them:

go get github.com/DATA-DOG/go-sqlmock

go get github.com/gin-gonic/gin

go get github.com/go-sql-driver/mysql

go get github.com/joho/godotenv

go get github.com/stretchr/testify

Step 2: Error Handling

We will need to handle any errors gracefully when they occur. Let's create a package that will help us do that.

From the root directory, create the utils directory,

mkdir utils

We will define the error_utils and error_formats packages.

i. error_utils:

This package has methods that handle different kinds of errors we may encounter in the application.

Inside the utils directory, create the error_utils package:

cd utils && mkdir error_utils

Then create the error_utils file:

cd error_utils && touch error_utils.go

With the content:

ii. error_formats

The essence of the package is format nicely database errors that may occur. For instance, The title field in this article is unique, so if we attempt to insert a value with a title that is already taken, a database error will surface. So we need to inform the user of the app nicely, rather than him seeing a text that has information of the database we are using, and so on.

Inside the utils directory, create the error_formats directory, then the error_formats.go file:

mkdir error_formats && touch error_formats.go

Step 3: The Domain(Model)

The domain houses the:

Data transfer object ( DTO ). See this as the struct that contains the data structure we are using in the entire application.

). See this as the struct that contains the data structure we are using in the entire application. Data Access Object(DAO). This is where we perform the logic for retrieving, saving and updating data in the database.

From the root directory, create the domain directory.

mkdir domain

i. DTO.

Then create the message_dto.go file inside the domain directory:

cd domain && touch message_dto.go

With the content:

Observe that we defined the method Validate() which is used to validate inputs.

ii. DAO

In the same directory, create the message_dao.go file

touch message_dao.go

With the content:

As seen, a lot is going on in that file. One of the most important things I want you to observe is the use of Interface. Using interface will enable us to mock the domain methods when we write unit tests for the services and controllers that will be defined later. The interface is also handy when we need to fake the database connections in each domain method.

Since this api is pretty basic, I decided not to use an ORM.

Unit Tests for the Domain Methods:

Will be testing the methods defined in the DAO. Observe that each of them have database interaction. For we to achieve unit tests, we must fake the database connection. From the message_dao.go file, we defined the NewMessageRepository(*sql.DB) function that implements the messageRepoInterface. This is where the “magic” happens. We can be able to swap the DB instance with a mock.

We will use go-sqlmock to test database interactions. This is essential to achieve unit test, else the test becomes an integration test with a real database connection.

Create message_test.go file inside the domain directory:

touch message_test.go

We used the Table Testing technique to test each method. Observe how we swapped the DB connection with the help of the NewMessageRepository function. No real database is hit, which thus helped us achieve unit testing in the domain methods.

Run the test suite:

go test

To run individual test use:

go test --run <test function> E.g:

go test --run TestMessageRepo_Get

Step 4: Services

Remember, we are building an API. At the end of the day, we will have controllers. The flow is: the controllers are in charge of all HTTP requests, the services handle the main logic in the api, the domain processes the data passed by the service and return a database response which may be the desired result or an error.

So, the domain methods are called in the services.

From the root directory(path: /efficient-api/) create the services directory:

mkdir services

Then, create the messages_service.go file:

cd services && messages_service.go

Using Interface is very vital here, to enable us to fake the service's methods when we are performing unit testing for our controller functions that will be defined soon.

In the test file, we mocked all the domain methods that are called. With the help of the Interface, we defined in the domain package.

Create messages_service_test.go file inside the services directory:

touch messages_service_test.go

From the test file above, we define the getDBMock struct that is used in place of the actual one. Then in each of the test cases, we made this fake struct implement the messageServiceInterface in this line:

domain.MessageRepo = &getDBMock{}

Without this line, the actual domain method is called. Take note.

Hence we achieve unit testing in our service methods. How Sweet.

Run the test suite in the services package:

go test

You can also run the test with coverage:

go test -cover

Step 5: Controllers

In the root directory(path: /efficient-api/), create the controllers directory:

mkdir controllers

Then the messages_controller.go file

cd controllers && touch messages_controller.go

From the above, we used the gin-gonic framework. Gin is a cool package for request handling, response handling, and routing.

Testing the controller functions.

Observe that each controller function called a service method. To achieve unit testing on the controllers, those service methods must be mocked. We will define a fake struct that implements the messageServiceInterface, hence allowing us to create the interface method(this time, the way we want it).

Inside the controllers directory, create the messages_controller_test.go file

touch messages_controller_test.go

go test -cover //running test with coverage.

All tests passed!

Step 6: Running the API

We need to perform basic crud(create read, update and delete) action on messages, so we need to define routing to help us navigate. From the root directory(/efficient-api/), create the app directory

mkdir app

Then create the routes.go file inside the app directory

cd app && routes.go

Still, in the app directory, create the app.go file:

touch app.go

You could see that we called the routes function we defined earlier, and we initialized the database connection.

Now, let start the app.

In the root directory(path: /efficient-api/), create the main.go file:

touch main.go

go run main.go

Your application structure should look like this:

The integration_tests directory will be created in step 7.

Now, you can use curl or postman to create, get, delete or update a message.

This is a sample post request:

Step 7: Integration Tests.

Thus far, all tests above have been unit tests. In this step, we are going to see an integration test that hits a real testing database. It a good idea that you also use a real database while testing.

In the root directory(path: /efficient-api/), create the integration_tests directory.

mkdir integration_tests

Remember, a real database connection will be involved here. So we will have functions that refresh and seed the database before any test is run.

Let’s create a file that will have this setup.

Inside the integration_tests directory, create the setup_test.go file:

cd integration_tests && setup_test.go

Ensure that you have created a test database and have the database details in your .env file.

You can write integration tests for the domain methods, as they interact with the database, the services methods by calling the real domain methods, or the controller functions that call the services, which call the domain methods that interact with the database.

We will only consider Integration tests for the controller functions, as they will call the services which call the domains that interact with the database.

Create message_controller_integration_test.go file inside the integration_tests directory:

touch message_controller_integration_test.go

We ensured that we used Table Tests, so we can test all edge cases with one test function.

No test function depend on the other, you can see that before any test is executed, the database function and the refreshMessagesTable are always called.

While in the integration_tests directory, run the test suite:

go test

All tests passed. How Sweet!

Note that the controller integration tests called the real service and domain methods. You can write the integration tests for the service and the domain methods if you wish.

Get the code on github. You can drop a star.

Conclusion

Both Unit and Integration tests are important in an application. They give the developer more confidence, they also help identify bugs. Writing tests can save you from a lot of trouble. I understand that it is tedious to write sometimes, but it is worth it.

Thank you.

You can follow me on twitter for notifications on new articles.

Happy Testing.