Welcome

Another way to test Node.js CLI tools

I’ve been writing a few CLI tools using Node.js lately. All of them require asynchronous code at some point in their calls; but some have multiple. Ideally, I’d expose each function in a module properly and test them, but I wanted to be able to test the end users experience also, where they call the CLI tool and get different results back. I could mock the HTTP requests, but I need the actual calls to execute.

Let’s test this

While chatting to a few friends, I thought why not just spawn off the call and evaluate the response. The particular tool that I was writing had multiple functions you could execute and calling each one allowed me to test the majority of my code. I chose to use Chai/Assert for my tests. Chai allows you to run asynchronous tests by passing in a “done” object into your test. When you execute that object, it will complete the test for you. Synchronous calls are tested normally as shown below.

1
2
3
4
5
6
7
8
9
10
var cli = require('../src/index')

describe("Object functions", function(){
it("should have a run function", function(){
cli.should.have.property("run");
});
it("should have a help function",function(){
cli.should.have.property("help");
});
});

Spawn to the rescue

Spawn allows us to execute the CLI code, with parameters, and capture the output to evaluate. This allowed me to closely replicate an end users experience using the CLI tool. I decided to create a known passing and failing test for each asynchronous operation. spawn allows us to capture the standard output and standard error from the CLI call. If there are any errors, we can check to see if the test should pass or fail.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
it("Should return the banner and commands",function(done){
var banner=spawn("../bin/tool",[""],{});
var buffer=null;
banner.stdout.on("data",function(data){
buffer+=data;
});

banner.stderr.on("data",function(err){
assert.ok(false,"Error "+err);
done();
});

banner.on("close",function(){
buffer.indexOf("Tool Title").should.not.equal(-1);
done();
});

});

The second parameter of spawn is an array of arguments to pass, allowing me to test each of the calls available. We buffer the standard output to compare against in the close event. In our case below, we are only looking for the string “Project Created” to let us know a new project was created.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
it("Should return the banner and commands with invalid arguments",function(done){
var banner=spawn("../bin/tool",["create","foobar"],{});
var buffer=null;
banner.stdout.on("data",function(data){
buffer+=data;
});
banner.stderr.on("data",function(err){
assert.ok(false,"Error "+err);
done();
});
//On the close event, we look at the buffer
banner.on("close",function(){
buffer.indexOf("Project Created").should.not.equal(-1);
done();
});
});

Not the end all

This is not an end all solution for testing your Node.js code. This can just be in addition to however your current testing environment is.