Effective Ginkgo/Gomega

William Martin
The Startup
Published in
5 min readJun 19, 2019

I’ve been writing Go tests using the Ginkgo BDD test library, and its preferred matcher library Gomega for almost four years as of writing. In that time I’ve discovered some recurring practices in the projects that I’ve joined, and in those I’ve started, which hopefully may be of use to you. I hesitate to refer to these as “Best Practices”, instead titling this post “Effective”, because although these work for me in most cases, they may not work for everyone in all cases.

Occasionally I’m going to link to codebases I’ve worked on recently that follows some of these practices, some of the time (hey, I said these weren’t best practices all the time!) but if there’s anything unclear, please leave a comment!

It’s expected that you have some familiarity with these libraries (or others like it), to best understand these patterns. So, without further ado:

One JustBeforeEach per test hierarchy

Consider having one JustBeforeEach at the top level performing some shared action for your test hierarchy (typically bounded to a file, unless you have suite level setup and teardown).

For example, if you are testing a CLI command, have a JustBeforeEach that runs some set of arguments that can be modified by other BeforeEach blocks. You can find an example of this pattern in the ism codebase but here’s an inline snippet (pseudocode) demonstrating how you might use this pattern for testing a binary and its arguments:

Notice how the arguments to the binary are declared at the beginning and then modified throughout the tree structure in BeforeEach blocks. As a result, we only had to run the command once in theJustBeforeEach block. The alternative to this model generally is to run the command in each It as the first line butI prefer JustBeforeEach to reduce the visual noise, and to keep only assertions inside my It blocks.

Caution: Don’t get clever and keep your hierarchy simple. JustBeforeEaches break the descending tree structure and make reasoning harder so avoid having more than one.

BeforeEach should act out the description of its closest Context or Describe

Prefer having Context and descriptions immediately being followed by a BeforeEach that takes the relevant action. This allows readers to very easily understand exactly how state is being modified throughout the test hierarchy. Let’s look back at a snippet from the example above and notice how each BeforeEach takes exactly one action that corresponds to the text description above it:

This pattern holds true for more complicated setup as well. Looking into the ism codebase, here is a description stating that a broker will be registered (don’t worry, you don’t need to understand what a broker is), which is immediately followed by a helper function that takes registers a broker. Such clarity!

If you find a BeforeEach doing something that isn’t in a description, consider whether the tests should be structured in some other hierarchy.

Helper functions over expectations in setup and teardown

Instead of setup and teardown directly making assertions, prefer helper functions that use ExpectWithOffset. Take a look again at the example from the previous section where a broker is being registered, then let’s look at the code that function is encapsulating — I definitely don’t want to see that code everywhere we register a broker. In this case, the authors have decided not to use the variant WithOffset which will result in Ginkgo printing a failure stack trace that is off by one (see the tradeoff below for why this might be acceptable).

The utility of expectations failing in setup and teardown is useful, but the code can get visually busy very quickly. Most of the time when reading a test you want to quickly parse intent through a well chosen name. It can be easy to allow these helper functions to increase in complexity as you just need a little extra behaviour, but this will cause you pain; ensure that your helper functions are extremely narrow in their scope, acting as thin wrappers that assert correctness e.g:

Caution: using ExpectWithOffset couples your code to its calling hierarchy due to the stack frame unwinding. Be very careful if you are going to refactor around the helpers that offsets are correct.

Randomise and parallelise from the start

Prefer starting your projects with full randomisation and parallelisation on in Ginkgo via the --randomizeAllSpecs and -p flags. The cost of hunting test flakes caused by turning these on later can be huge.

See Ed King’s great blog post “The Night is Dark and Full of Flakes” for our experience on the Cloud Foundry Garden team after not following this advice.

Consider the contract Eventually and Consistently place upon your implementation

Eventually and Consistently are incredibly useful functions that provide the ability to assert that some state will exist within some timeout (defaulted to one second).

These utilities are extremely helpful but they are also placing a hidden constraint on your code (that some action should be time bounded) and may hide other issues such as deadlocks. If you find yourself bumping timeouts to appease these functions, consider whether you should be using something else.

On one project, after experiencing one too many failures that we were unable to debug, we removed the timeout altogether and wrote a tool called slowmobius that watched over all our test runs and notified us of any that were taking an unusually long time to complete. This allowed us to better distinguish environmental slowness from hangs, and gave us the ability to jump in and debug.

Use goroutines in tests with care

This one is quite simple, be careful with goroutines in your tests, they don’t play well with Ginkgo’s failure model, and if they aren’t carefully managed they can leak between tests.

Dot import the testing libraries

Dot imports in Go pollute your namespace with public functions and types from the imported package — typically this is a big no-no! I suggest dot importing ginkgo, gomega and its friends gexec, gbytes, and ghttp. However, if you want to see:

Be my guest.

Don’t even think about using the Omega symbol

When Gomega was first created, you could use Ω instead ofExpect e.g.

Ω(foo).To(Equal(bar))

This still lurks around in tests we haven’t got round to cleaning up yet. This should be self-explanatory. Don’t do it. Seriously.

Not a joke

What else?

Have you got any more effective practices? Do you disagree with any of these? Leave a comment!

If you’re interested in reading more, check out my next post on common Ginkgo and Gomega gotchas.

Thanks for reading!

--

--