Unit testing with a DB server running in docker is a much better choice than Sqlmock, usually
About two years ago, I wrote an article discussing how to test a GORM application with Sqlmock.
Time flies, and I realize that most developers do not adopt this approach (at least on my team). Writing a test case using Sqlmock is too complex to get started.
The core issue is that you need to write up the whole SQL snippet manually, then compare it with the output from GORM. This workload is already much more than writing the codes to be tested. After all, the reason we use GORM is to avoid writing raw SQL manually.
We need to find a better way.
In general, testing DB application is hard. The main reason is the database server itself.
- If the dev team shares a remote database server, then data conflict is inevitable.
- If we create accounts for every developer on this database server, then unit tests should be run with different accounts. And someone needs to maintain these accounts.
- If we demand every developer install and set up a database server on their local workstation, we increase the difficulty of setting up a development environment.
Seems there’s no answer is good enough. But if we could run the database server locally with docker and integrate the test case with docker, that would be perfect.
Image process below:
- Start a database server with docker before each test suite. open a GORM connection to this server
- Before every test case runs, clear data in the database and re-create tables if needed
- Run all test cases
- Stop the database server after test suite execution
In theory, we could control the docker daemon with a command line. But with the help of dockertest, this target could be achieved easily.
Here’s a step-by-step tutorial for unit testing a GORM application with a real database server running in docker.
we’ll re-use the example application in the previous article. You can find the source code on Github.
We take Postgres as an example in this article, but it could work with any other database.
Setting up test suite
According to the sequence diagram, we do a series of preparations:
BeforeSuite, we create an instance of
*gorm.DBand a function to clean up docker resources. The function
setupGormWithDockerwill be explained later
AfterSuite, we call
cleanupDockerto release docker-related resources.
BeforeEach, we drop the default schema and then re-create it to make sure the database is clean and ready before every test case run.
Below are the explanations for the core function
- Create a docker resource pool with
dockertest.NewPool, which is used to run the docker container
- Specify image name, image version, and environment variable with
fnConfigis a function to control bootstrap policy. In this case, we need the docker container to be auto-removed after stop and never auto-restart
- We use
pool.RunWithOptionsto run the container, then create the cleanup function
- Since it will take some time to start the container, we need to wait until the container is ready. Only after that, we can return the
pool.Retrycomes to the rescue.
pool.Retrywill execute the parameter function repeatedly until the function returns
nil(which means the container is ready)
Building test case
With the preparations in the test suite, we could get a usable instance of
*gorm.DB, connecting to a local database server, and the database will be cleared before every test case execution.
BeforeEachwe create an instance of
Repositoryfor testing. Calling
repo.Migrateto create tables automatically, then create sample blog data.
- The test cases are pretty intuitive. We call the repo methods, and verify the expected result is returned. There’s no mock here since we’re using a real database server.
- Compared to Sqlmock, there’s no need to write raw SQL manually, and we could finish the entire test case in less than a hundred lines of code.
- dockertest will pull images if necessary, but without a download progress prompt. Our suggestion is to run
docker pull postgres:14before running the test case for the first time.
- Running the test suite may take several seconds since we need to start up the Postgres database server. It may feel slower than most in-memory unit-test, but it is acceptable.
pool.Retry, some connection errors will be output. If you don’t like the disturbance, pass
gorm.Opento close log
- Your application may depend on some Postgres extension. In this case, you can replace the standard Postgres image with a custom one. For example, if you need postgres all, you could use this docker image
- Testing the GORM application against a real database server has a huge advantage.
- With the help of
dockertest, a local docker container could work with golang unit testing seamlessly.
Sqlmock, all the DB logics are running on a real database server, there’s no need for any mock, and test cases are significantly simplified.
- Is there any reason to stick with Sqlmock now?
- For the complete source code, please visit its repository.