Writing unit tests is simple. Verifying complex CLI tool behavior on the other hand is hard. Sometimes a bug appears for certain inputs and it requires lots of human debugging via log messages before the true cause is located. Can we automate at least the end to end regression tests?
We can, if instead of human log messages we output JSON messages, for example using bunyan or winston logger. Then we can easily parse the program's output and verify what has happened.
My own TDD QUnit-like test runner gt has an assertion gt.exec that runs any given command and verifies the exit code, or returns the program's standard output to the unit test for further validation. If the program outputs JSON objects, you can easily find them and verify the events.
Logger setup
I use bunyan and recommend only outputting
JSON objects when necessary, for example, if the user
called the program using --debug
command line argument.
1 | var bunyan = require('bunyan'); |
The JSON objects can be quickly printed by piping the program's output through the bunyan CLI tool
$ node index.js | bunyan -o short
hi from console
03:12:41.848Z INFO myapp: message { foo: 'foo', bar: 'bar' }
03:12:41.849Z INFO myapp: message 2 { foo: 'foo', bar: 'bar' }
Unit tests
The end to end unit test executes the separate program asynchronously using gt.exec passing the program name and its arguments. It also should pass the expected exit code and a callback function that can further verify the standard output text. To simplify parsing and finding the objects with given label, I wrote bunyan-gt that provides a single helper function:
1 | var bgt = require('bunyan-gt'); |
The bgt(stdout, loggerName, messageLabel) function finds messages first by the logger name, then by the message label. The returned array contains objects by the label
1 | log.debug('my label', { foo: 'foo' }); |
Conclusion
While this end to end testing approach might not replace mocking objects and integration tests, it is significantly simpler to implement and use. It also mimics the manual debugging approach, and could be used to write simple regression tests after the debugging has found the problem.