Writing unit tests in Go ain’t the easiest thing to start with.
Note that I didn’t say that it’s hard to write unit tests in Go. Rather, the “vanilla” test tools may not represent the most intuitive usages, to begin with. However, if you know where to look, it is possible to write effective and understandable tests for your Go applications. It’s even possible to integrate these tools into your environment, and use them to enhance your team’s experience with testing.
I’m here today to share 3 tips I’ve learned over the years, in testing with Go.
Tip #1 — Behaviour Driven Development (BDD) with Ginkgo and Gomega
Programmers familiar with the BDD interface would understand how common methods, such as
Expect , works in creating a comprehensive coverage of tests for your application. However, this is not available natively in Go, as seen below.
Well, this dilemma can be easily resolved by adding Ginkgo and Gomega. Ginkgo provides the BDD testing framework that we desire, while Gomega provides the matcher methods that work seamlessly with Ginkgo.
Here’s an example of how Ginkgo and Gomega can be used for the same test above, presented in a more readable manner:
1. Install Ginkgo and Gomega to your project:
go get -u github.com/onsi/ginkgo/ginkgo
go get github.com/onsi/gomega/...
2. Change directory to the target package that you want to test
3. Run the following bootstrap command in the intended directory:
4. Generate the test file to implement your tests:
ginkgo generate %FILENAME%
Once step 4 is completed, the test file should contain the imports for Ginkgo and Gomega, and you would only need to add your tests from there. If you need to generate more test files, simply repeat steps 2–4 above.
Tip #2 — Mocking calls and return values with GoMock
When planning and implementing unit tests, isolation is one big concern. While testing the logic within a method, we want to be able to mock its dependencies, such as other functions that it calls, and control the return values.
In Ruby, using RSpec mocks, it should look like the above — where we can set an expectation for a method to be invoked, and mock the return to ensure isolation in tested logic. In the above example,
getPage is expected to be invoked with no arguments, and is mocked to return an integer value of
The functionality to isolate dependencies is not available natively in Go. However, you can make use of interfaces and the gomock library to mock said interface dependencies, in three simple steps:
1. Install the required library with the following command:
GO111MODULE=on go get firstname.lastname@example.org
2. Find the package and file that you want to mock and run the following command:
mockgen -source=pathTo/file.go -destination=pathTo/newMockFile.go
3. Import the generated mock file package in your test file, and use it to initialize the mock instance
Once you have imported and initialize the variable, simply use the variable in place of the interface value (e.g. passing as input to a function). If you are interested to see this in action, view the highlighted code here.
And voilà! That’s all it’s needed to mock your interfaces for unit tests. If you need to generate more mocks, simply repeat steps 2–3 above.
However, if the mock library doesn’t work out for your testing, there are also other alternatives. A suggestion here is the monkey package for monkey patching receiver methods, or if you using a specific technology, like SQL datastores, go-sqlmock works wonders here.
Tip #3 — Use the Go CLI for more than just simple testing
The most common way to execute tests, is to use the command
go test . Using this command, you will get a simple summary of passed and failed tests, and understand other issues, such as packages with no implemented tests.
While this is one way to get your test results (and also useful for automation like in CI), we can stretch it even further to get more context on coverage, with an intuitive UI for understanding coverage on each and every method as well as lines of code:
The above is done using a combination of
go test commands, with the
coverprofile option to save the coverage output to a file. The
go tool cover command is then used with both non-HTML and HTML options to present the coverage output for each go file and method, in the terminal and web browser, respectively.
These commands are implemented in the project’s makefile (where you can see the invocation in the video above being
make coverage ). This makes it easy to bundle
go test and
go tool cover commands to create a helpful test environment, as we have achieved here.
All these tools are used in conjunction with development in a local docker container. Running in a containerized environment allows us to host a lightweight Nginx server on the side, and to view the generated coverage files in HTML directly!
All in all, we’ve looked at using BDD with Ginkgo + Gomega, Gomock for mocking / isolating logic, and enhancing the usage of
go test for coverage with Docker and a makefile config, making it easier for developers to implement unit tests.
The example project that I referenced several times above can be found here — example-test-go
That said, there’s definitely more that can be done in terms of testing in Go, and much more to make testing easier. However, with the above tips, I hope that provides enough details to get you started on writing those tests!