Go Testing

Golang MySQL Integration Test

Integration Test with testify Suite, sure you’re gonna need it …

David Yappeter

--

source: github.com/shalakhin/gophericons

When you are creating a go application and deploy it without testing, you will often find a bug in your code. That’s not a good idea to deploy a not-tested code. Normally there are 3 types of testing in web development:

  • Unit testing is to test the smallest modules individually, or we can say that we test each function. Normally tested by the developer or CI (Continuous Integration), it is the cheapest and easiest one of the three tests. The amount of this test is fairly large.
  • Integration test is responsible for combining some section of our application that is part of our program flow and making sure the interaction between section is correct. For example, user register flow. The users cannot register 2 duplicate emails, etc.
  • Functional test is the final test before the code is deployed to the live environment, this is the QA job and of course, this test costs the most money and time.

For this article, we will test the Integration Test to our Go-MySQL database code.

Requirement

Some basics of Go Programming will be enough. For the http router, you can uses net/http , I prefer gin because of the feature.

We will use testify to help us with the testing later on, and GORM is a go-ORM library for SQL database, which will help us with the database query.

Repository: https://github.com/david-yappeter/go-mysql-suite

Get Started

Initialize a go module.

$ mkdir go-mysql-suite && cd go-mysql-suite && go mod init myapp

Our project directory and files will look like this in the end.

.
├── config
│ └── gorm.go
├── controllers
│ └── user.go
├── entity
│ └── user.go
└── migrations
│ ├── migration.go
├── service
│ └── user.go
└── tests
│ ├── suite_test.go
│ └── user_test.go
├── .env
├── go.mod
├── go.sum
└── server.go

First, we will write our server.go

server.go

server.go

func init() will be called first before main() start, it will load .env config when we start the program. Then we call config.ConnectGorm() to initialize a database connection, we will write the code later, and we call defer sqlDB.Close() to close the database connection after our code stopped.

We will provide 2 main endpoints here: /users (GET), /users (POST) , the first one is to get an array of users and the second one is to create a user. The controller will be initialized later inside the controllers/user.go .

Next, we will jump into config/gorm.go

config/gorm.go

config/gorm.go

In this file, we initialize a global variable db *gorm.DB which store our database pool, and ConnectGorm() is called in the server.go will assign a value into db when the code is running. Our getter will be GetDB() which return the value of the db .

Other functions initConfig(), initLog(), initNamingStrategy are just GORM config.

Then we will create our User entity in entity/user.go

entity/user.go

entity/user.go

We will create a simple User to make the CRUD shorter. binding tag is for gin binder later on, and gorm tag will help us with the model migration.

Let’s move on to the controllers

controller/user.go

controllers/user.go

Our User controller has a global variable UserController which will be called in the server.go and contain 2 function Create and GetAll . The function will get/create user data from service module which we will create later on.

For the service we will create 3 function UserCreate ,UserGetAll , UserGetByEmail

service/user.go

service/user.go

We will use github.com/google/uuid to hash the user.ID when inserting the data into the database and UserGetAll will return all users from the database. And of course, we will need to check if the email has been used before or not before inserting the data.

Lastly, we need to make the migrations before jump into the testing

migrations/migration.go

The migration is simple, you just need to call db.AutoMigrate along with all models to be migrated.

The .env will contain this configuration, changed it according to your’s

DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=root
DB_PASS=
DB_DATABASE=go_mysql_suite

This code is already working fine, but we will create a test from here.

Suite Test

tests/suite_test.go

suite.Suite from testify provide us 4 interface that we can use: SetupTest, TearDownTest, SetupSuite, TearDownSuite .

  • SetupSuite will run first to Initialize the Suite data needed.
  • TearDownSuite will run the last, after all, tests are done.
  • SetupTest will run before a test.
  • TearDownTest will run after a test.

TestSuite function will trigger the test, and the name of the function must start with Test (prefix).

We initialize database connection and migration in SetupSuite , and then closing the connection and drop all tables in TearDownSuite . We set our testing env by using os.SetEnv and later on os.Unsetenv to avoid overwritten config in our terminal session.

Next is how to make a test with the suite that we have created.

tests/user_test.go

The prefix of each test function MUST start with Test , inside of the tests we can call our service and make a flow, for example, we create a user with first@gmail.com and then second@gmail.com . Of course, we are expecting NoError so we will use testify submodule called assert that helps us with the conditional assert.NoError, assert.Error , etc.

When we try to insert the third user with second@gmail.com we are expecting an error from this function because the email was found in the database.

Let’s try to run the test.

$ go test ./testsMy Output:
ok myapp/tests 0.245s
// Verbose
$ go test -v ./tests
My Output (Verbose):
=== RUN TestSuite
--- PASS: TestSuite (0.16s)
--- PASS: TestSuite/TestCreateUser (0.02s)
PASS
ok myapp/tests 0.293s
PS D:\go\src\go-mysql-suite> go test ./tests
ok myapp/tests 0.245s
PS D:\go\src\go-mysql-suite> go test -v ./tests
=== RUN TestSuite
=== RUN TestSuite/TestCreateUser
--- PASS: TestSuite (0.16s)
--- PASS: TestSuite/TestCreateUser (0.02s)
PASS
ok myapp/tests (cached)

Conclusions

It’s not bad to have a test in your code, maybe it took you long enough to write the test. But trust me, that it will help you later on, it saves your time, and energy to recheck every time before you deploy it. And the best part is, you can include a go test in CI/CD.

That’s all for this article, hope you have a great day :).

--

--