Unit testing in Go with Ginkgo: Part 1
At Bold, many of our back-end services are written in Go. On any given day, over 80,000 eCommerce websites use our software to power their online sales. Any downtime or severe bugs could result in lost sales and revenue for these merchants, which is why it’s important that we focus on delivering high-quality production code.
Test-driven development (TDD) is an important practice in our software development process. It helps give us confidence that the changes we’re introducing won’t cause any unintended side effects.
Go has a fairly capable built-in testing package, however it does have some limitations. This article will explain (with some simple examples) how we use Ginkgo and Gomega at Bold to write more expressive and structured unit tests in Go.
Background
Let’s first take a look at Go’s testing
package and toolchain:
For example, I have a package adder
which provides an Add function that sums two integers.
Using the testing
package, a test could be written like this:
Run the test in your terminal with go test
.
$ go test PASS ok
github.com/markstgodard/adder 0.006s
Great, a passing unit test, but what if my function has a bug and subtracts instead of adds? What would the output of a go test
look like?
$ go test
--- FAIL: TestAdd (0.00s)
adder_test.go:8: Sum, got: -1, want: 5.
FAIL
exit status 1
FAIL github.com/markstgodard/adder 0.006s
This is fine for basic use, but the built-in Go testing
package has two major limitations:
- It only provides a simple XUnit style of tests
- It does not provide a library for assertions
Enter Ginkgo 🎉
Ginkgo is a Go unit testing framework, created by Onsi Fakhouri, that provides for a BDD style of unit tests (think RSpec, but for Go).
Getting started
To get started with Ginkgo, follow these steps.
Download Ginkgo
Install the latest version of ginkgo with go get
.
$ go get -u github.com/onsi/ginkgo/ginkgo
$ go get -u github.com/onsi/gomega/...
Create a Suite
Ginkgo requires a test suite for each package that your program uses.
$ mkdir adder && cd adder
$ ginkgo bootstrap
Generating ginkgo test suite bootstrap for adder in:
adder_suite_test.go
This will create a test suite to bootstrap running your tests.
Create a new unit test
Now that we have a suite, let’s create our first Ginkgo unit test!
$ ginkgo generate
Generating ginkgo test for Adder in:
adder_test.go
This will generate an empty test file, into which you can start adding individual unit tests.
Now, let’s update and add our first test:
Running unit tests
To run our unit tests, we run Ginkgo.
$ ginkgo
Running Suite: Adder Suite
==========================
Random Seed: 1552860025
Will run 1 of 1 specs •
Ran 1 of 1 Specs in 0.000 seconds
SUCCESS! -- 1 Passed | 0 Failed | 0 Pending | 0 Skipped
PASS Ginkgo ran 1 suite in 1.549965067s
Test Suite Passed
Now, let’s say our requirements change and we’re only allowed to add positive numbers. Using the TDD process, let’s start by adding a failing test.
We can use Context
to indicate specific pre-conditions, such as when our inputs are valid or not valid:
Now, we’ll re-run our tests.
$ ginkgo
Running Suite: Adder Suite
==========================
Random Seed: 1553135447
Will run 2 of 2 specs•
------------------------------
• Failure [0.001 seconds]
Adder
/Users/mark/go/src/github.com/markstgodard/adder/adder_test.go:10
Add
/Users/mark/go/src/github.com/markstgodard/adder/adder_test.go:12
when summand is negative
/Users//go/src/github.com/markstgodard/adder/adder_test.go:24
returns an err [It]
/Users/mark/go/src/github.com/markstgodard/adder/adder_test.go:26Expected an error to have occurred. Got:
<nil>: nil/Users/mark/go/src/github.com/markstgodard/adder/adder_test.go:28
------------------------------Summarizing 1 Failure:[Fail] Adder Add when summand is negative [It] returns an err
/Users/mark/go/src/github.com/markstgodard/adder/adder_test.go:28Ran 2 of 2 Specs in 0.001 seconds
FAIL! -- 1 Passed | 1 Failed | 0 Pending | 0 Skipped
--- FAIL: TestAdder (0.00s)
FAILGinkgo ran 1 suite in 1.658112523s
Test Suite Failed
Okay, so we have a failing test. Time to update our function to get the test to pass.
Now, we’ll re-run them so that we should have two passing tests.
$ ginkgo
Running Suite: Adder Suite
==========================
Random Seed: 1553135928
Will run 2 of 2 specs••
Ran 2 of 2 Specs in 0.000 seconds
SUCCESS! -- 2 Passed | 0 Failed | 0 Pending | 0 Skipped
PASSGinkgo ran 1 suite in 1.789141688s
Test Suite Passed
Tip: Using -v provides more of a human-readable description of your unit tests.
Summary
Ginkgo is a powerful unit testing framework, providing a BDD-style DSL that runs on top of the standard Go testing
infrastructure.
Ginkgo provides the DSL that allows developers to compose tests in a more expressive way.
Describe
blocks define the thing you’re testing.
Context
blocks define the when of the test (i.e. “when invalid request is received,” “when service is unavailable”).
It
blocks run the code to test and state what you expect to happen.
BeforeEach
blocks run before each unit test ( It
blocks).
AfterEach
blocks run after each unit test.
Next steps 🚀
Ginkgo helps you write more expressive unit tests in Go (like the one we did today) to make your program safer and more dependable.
Our next post will take a deeper dive into Ginkgo, including:
- More advanced assertion matching using Gomega
- How to generate “fakes” to mock your dependencies
- How to write tests with concurrent code
- Tips and tricks we’ve learned along the way