Go Testing
Golang MySQL Integration Test
Integration Test with testify Suite, sure you’re gonna need it …
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
- github.com/gin-gonic/gin (optional)
- github.com/stretchr/testify
- gorm.io/gorm
- github.com/google/uuid (optional)
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
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
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
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
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
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 ./testsMy 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 :).