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

We now have an out which makes it much easier to capture output. We no longer use the functions in the flag package. Instead we have to instantiate a new flag parser for each call to main() . 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: