Unit Testing CLI Programs in Go

Elliot Chance
The Startup
Published in
2 min readFeb 29, 2020
Photo by John Schnobrich on Unsplash

It’s a common scenario — for me at least — to be building a CLI tool (main package) that has CLI options (flags package) that I want to add unit tests to.

Here’s a simple example of a CLI tool that adds or multiplies some input numbers:

Let’s try it:

5 + 7 + 9 = 21 — great… But wait! 3 * 2 * 5 != 0…I’ve put a deliberate bug in the command. Something that we will catch and fix with a unit test.

Don’t Try This At Home Kids

Let’s write a unit test that uses the same CLI arguments so we can fix the bug. However, we have a lot of global state that needs to be manipulated:

Yuck! This is really ugly and problematic code… miles away from a small, easy to read unit test.

One fairly obvious solution here is to separate the logic that does the calculating from the main() function (that handles the arguments and output), like:

Now the unit test is trivial:

But this is cheating! Since we have bypassed the flags completely, we are not actually testing the CLI arguments. We’d really like to perform black-box testing on the tool as it would be used — instead of testing isolated parts. Especially if a bug was introduced to the way flags are parsed.

A Better Solution

  1. We now have an out which makes it much easier to capture output.
  2. We no longer use the functions in the flag package. Instead we have to instantiate a new flag parser for each call to main() .
  3. Anywhere output is generated will have to write to out instead.

Now our unit test needs only know about the CLI arguments before calling main():

This looks a lot nicer when there are multiple tests:

--

--

Elliot Chance
The Startup

I’m a data nerd and TDD enthusiast originally from Sydney. Currently working for Uber in New York. My thoughts here are my own. 🤓 elliotchance@gmail.com