“Tests are stories we tell the next generation of programmers on a project.”

Roy Osherove, The Art of Unit Testing

Image by PublicDomainPictures from Pixabay

An application contains small amount of components, it could be a struct, function or class, we call it unit components, and our application reliability is depends on this unit components. We should make sure the unit components don’t break our application.

So, what is unit test? Unit test is a way of testing our unit components which should be isolated from our external system. Michael Feathers the author of Working Effectively with Legacy Code, said a test is NOT an unit test if:

  • Talks to the database.

Since we shouldn’t talk to database or network, how do we test a function which has responsibility for querying data from a SQL database?

We should mock it, mocking is a process we used on unit testing when the unit has external dependencies so we can isolate the unit and focus on the code being tested and not on the behavior or state of external dependencies. The dependencies are replaced by closely controlled replacements objects that simulate the behavior of the real ones.

Testing Pyramid (https://www.hibri.net/2019/06/10/the_testing_pyramid/)

Go Built in Test Library

In this article, we’ll talk about how to create unit testing in Go. Go already has built in testing package on its standard library, it provides the tools we need to write unit tests and benchmark tests . Some requirements to create unit tests are:

  • The test file should has _test.go suffix, since compiler will ignores files that end in those suffix when compiling packages.
Location of Test File

Table Driven Test

Based on its name, table driven test is a way of testing a function / method, by validating itself against multiple parameters and results. By giving table contains of multiple test cases, the test will simply iterates through all table entries and performs the necessary tests. The test code is written once and amortized over all table entries.

Non Table Driven Test

By using table driven test, we can centralize the test of a function in to a single function block.

Table Driven Test

There is no strict rule to use table driven test on your Go codebase, if you prefer to use expressive Behavior-Driven Development (BDD) style tests, you can take a look at Ginkgo library.

Let’s start creating unit test using built in Go standard library. Supposed we have a function to calculate factorial of a positive integer, and we put it on file named calc.go.

And then, we create test function called func TestFactorial(t *testing.T) on calc_test.go file alongside calc.go.

Inside the test function, we declare tests with an anonymous struct to store our table driven test cases. For each test case, we run it on its own subtest. Subtest is a construct in Go’s testing package that split our test functions into granular test processes. It runs our test case in a separate goroutine and blocks until our function has been executed.

We simply compare expected result with actual result using Go equals operator. If you want more advance assertion, you can use third party library such as Testify.

Let’s run our code using go test -cover {YOUR_PROJECT_NAME}/calc command, all test cases will run successfully and it show our test coverage for calc.

Success Test

Let’s make our test failed by adding new case, we will use 5 as n value, and we expect it to return 0, and of course it will return fail on TestFactorial/N_is_5 case.

Fail Test

Testing Using Mock

In unit testing, we should not talk to database, so for example if our function call another function perform database query, we should mock those function. To mock external resource in to our function / method, we should never call the function which is responsible for access external resource directly. We could use Dependency Injection and the parameter should depend on abstractions instead of concrete implementations, we could use Go interface as abstraction.

For example, we had an application to create article and it exposed through a HTTP end point. We use Clean Architecture to organize our code structure, and the file structure will look like this.

The entity doesn’t has any dependency to others layer it just a struct to hold our data. The repo is responsible for accessing external resource (Redis, PostgreSQL, API call, etc) and it depend on library for accessing those resource such as go-redis, sql, and http. For usecase layer, it responsible for executing our business logic, and it should only depends on repo module. And the last one, the delivery layer, it responsible for handling how the data should be presented to the users. Since our app is exposing the HTTP end point, the delivery layer will consists of some HTTP handler, and it will depends on our usecase layer.

Testing Repository Layer

On our example application, the repository layer will connect to a PostgreSQL database by using sqlx library. Since Go has implicit interface, we could extract sqlx function to an interface, and inject it to out repository layer, or we could use go-sqlmock to to simulate any sql driver behavior in tests, without needing a real database connection. I prefer using go-sqlmock library, since it already implementing sql/driver on Go standard library and also compatible with sqlx.

And here is our test for UserRepo implementation, we put it on user/repo_test.go.

We create table driven test cases and run each test case on a subtest as same as our previous example. But inside out subtest, we create a sqlmock instance, and set to sqlx, so we can control the behavior of our query by using sqlmock instance. And we add beforeTest hook for each test cases and inject the sqlmock instance through via our hook, so each test case could access its own sqlmock instance to prepare expected behavior. If we run our test, all the cases will be executed successfully, and if we change the expected result to invalid one, it will fail as expected.

As you can see, we don’t connect to real database on the test file, and even we don’t set up the actual DB connection yet on our main code. If you like using TDD on your project, you can create the test and prepare the expected query first before jump to actual implementation.

If you use different kind of resources eg: Redis, API call, Kafka, etc, you can use related mock library. Or if you don’t found suitable library, by using Go implicit interface feature, you can create your own interface for those libraries and create your own mock.

Testing Use Case Layer

Use case layer depends on our repository layer, so to test use case layer, we should mock our repository first. Since we create our repository based on interface, we could simply create another implementation to be used for test.

We could create custom implementation of our repository based on our test cases. But it a repetitive task since for each test case we need to create new implementation, by using gomock we could generate source code for a mock implementation based on given interface to be mocked. Remember how we use sqlmock on previous section? By using gomock we could create a universal mock of our repository and control the behavior based on our need.

Here is interface and implementation of our use case, let’s call it RegisterUserUseCase , the use case is responsible for registering new user. So we will need UserRepo as a dependency on that use case.

After installing gomock, let’s create the mock of our UserRepo so we can use it on RegisterUserUseCase by running this command on our terminal.

It will generate our mock file on internal/user/repo_mock_test.go , for simplicity, I just create the mock file on same package as implementation, some people prefer to put the mock file on difference package, but feel free to adjust the file location and package based on your need. Here is the example of newly generated UserRepo mock file by gomock.

For testingRegisterUserUseCase, we create new test function TestNewRegisterUserUseCase(t *testing.T) on /user/usecase_test.go.

We inject instance of MockUserRepo to our use case, and we can control its behavior on beforeTest hook since mockgen provide us some additional method to MockUserRepo instance. You can see list of generated method on gomock github repository.

Testing Delivery Layer

Since we use HTTP to expose our application, we create POST /users as an end point for registering new user. And we will implement the HTTP handler for that end point.

We need to generate mock for RegisterUserUseCase using gomock just like we create mock for our UserRepo. And forhttp.ResponseWriter, Go standard library provided us with httptest package, so we don’t need to create mock for it.

By injecting the implementation of RegisterUseCase mock, we simulate success and fail scenario, and we compare the status, headers and body of HTTP response which is provided by httptest.ResponseRecorder. And if we run our test file, all test should be succeed.

We could implement unit test on Go project by using its built in library. And by managing dependency between component via interface, make us easier to mock the implementation and controlling the behavior of mocked dependency for our unit test.

You can see full source code for the article on this repository, Thanks.

Reference

A lifelong learner