Testing NodeJS with Mocha and Chai
Last weekend, I worked on a coding challenge that involved writing two NodeJS servers with unit tests. The NodeJS portion of the project was pretty straightforward. One server generates random arithmetic problems and POSTs them to the other server. The second server accepts the POST, parses the equation, solves it, and responds with the answer. The code is available on Github.
It was a fun project to work on. The only part that I had trouble with was the unit tests.
I used Mocha as the test runner and Chai as the assertion library. I mostly used the Chai Should syntax, as that was the most natural. In a couple places, I used the Expect syntax in places where that felt a little more natural. I wanted to do a little end-to-end testing for my API, so I used the Chai-HTTP library to help with the HTTP calls. Chai-HTTP is a library that wraps the Superagent library. Basically, it exposes the Superagent functionality and adds some additional assertions to the Chai AssertionError object. Superagent is a library for making HTTP AJAX requests.
Let me show a couple examples:
In this first example, I’m testing the equation parser to make sure that it can find the operand (+, -, *, etc) in the equation.
describe('parseOperand', function () { it('should return a plus sign', function() { equationSolver.parseOperand('1+2=').should.equal('+'); }); it('should return a minus sign', function() { equationSolver.parseOperand('1-2=').should.equal('-'); }); it('should an error if the operand isn\'t found or is not supported', function() { expect(function() { equationSolver.parseOperand('1^2=') }).to.throw(Error); }); });
In these examples, describe
and it
are part of the Mocha framework. describe
groups a number of test together. it
denotes the start of a test. equationSolver.parseOperand('1+2=')
is a call to my code. .should.equal('+')
is a Chai test on the response coming back from my code.
The first two examples are very straightforward and natural to read. However, things got a little interesting when it came time to test for errors.
The problem was that if I wrote test #3 in the same way as tests #1 & #2, the error thrown would cause the whole set of tests to error out. This is solved by wrapping the call in an anonymous function and then wrapping that in the test. Here, I felt the Expect syntax was a little more natural. It’s clear that the .expect()
is catching the results of the anonymous function. I didn’t try this with the Should syntax so I’m not sure if it would work or not.
In this next example, I’m testing the API by making an HTTP call to it and checking the response.
describe('POST', function () { it('should respond', function (done) { chai.request(server) .post('/api/equation') .send({ equation: '1+2=' }) .end(function (res) { res.should.have.status(200); done(); }); }); it('should respond with the correct answer', function (done) { chai.request(server) .post('/api/equation') .send({ equation: '1+4=' }) .end(function (err, res) { res.body.solution.should.equal(5); done(); }); }); });
The syntax here is mostly similar to first example. describe
and it
perform the same function here.
The body of the test is different, however. chai.request().post().send().end()
uses Superagent via the Chai-HTTP library. This test makes a POST out to the server (which is defined earlier) on the specified API endpiont, and sends a data object. end
is called to tell Chai-HTTP that you are done adding data to the request and that it can send it.
Once the data is returned from the server, the callback function is called and passed an error object and the response. Chai-HTTP can also a .then()
syntax with success/failure functions, if you prefer that.
Inside the end callback is the actual test. Chai-HTTP extends the Chai assertion library and adds convenience functions like .should.have.status()
to enable easy HTTP response testing. The second test simply tests the body of the response for an object called solution
and ensures that it is equal to 5 (the answer of ‘1+4=’).
Also inside the end callback is the most important part of the test! Mocha normally assumes that your tests are running synchronously (i.e., the results will be immediately available to the test). But Chai-HTTP runs the tests asynchronously. To indicate to Mocha that the data is available to the test, you must pass a done
function into the it
test and then call done()
inside the end
callback function. If you don’t do this, the test will always pass, even when it shouldn’t.
This done
function doesn’t need to be defined anywhere; it is literally just adding the word done
to the anonymous function and then calling done()
once the HTTP call has returned.