Recently I had the idea to attempt unit testing of my node.js codebase. There are some decent resources out there that cover this concept, and this blog post is a summary of my own thoughts and findings while implementing unit testing for node.js.
Lets start with the basic issue: testing asynchronous code is not straight forward. For most unit testing (and testing in general), you assume items will move in a linear fashion: follow steps 1-3, and you should see some expected result. Because node.js forces you to work in a more async fashion, you may not necessarily know when something is complete, which forces us to look for other ways to find out when that something is complete.
I’ve written below a contrived and simple node.js app, using express and mongodb, with a login mechanism. We want to unit test the login mechanism. The app may look something like this:
// login.js
var
express = require('express'),
app = express.createServer(),
// Database Config
mongo = require('mongojs'),
mongoStore = require('connect-mongodb'),
db = mongo.connect('dbname',['users']);
// Configuration
app.configure(function(){
...
});
app.listen(3000);
app.post('/login', function(req, res){
var email = req.body.email, password = req.body.password;
db.users.find({'email':email,'password':password}).forEach(function(err, user) {
res.send('Found user ' + user);
});
});
As you can see, it’s pretty straight forward: a POST request containing an email address and password are searched for in the mongodb instance. If that user exists, then return that user object.
Now, lets take a step back for a moment and start building a simple unit test for this mechanism. To do this, I’ve been using nodeunit, which provides a reliable and simple framework for executing tests, and reporting results. Here is an example of how we’d like to write the test:
// login-unit.js
var login = require('login.js');
exports.testLogin = function(test){
test.notEqual(login.loginUser('email', 'pass'), null, "The user was null!");
test.done();
};
Clearly our application code can’t yet support this unit test. The login logic needs some refactoring so that it’s externally accessible:
// login.js
var
express = require('express'),
app = express.createServer(),
// Database Config
mongo = require('mongojs'),
mongoStore = require('connect-mongodb'),
db = mongo.connect('dbname',['users']);
// Configuration
app.configure(function(){
...
});
app.listen(3000);
function loginUserPrivate(email, pass) {
db.users.find({'email':email,'password':password}).forEach(function(err, user) {
return user;
});
}
app.post('/login', function(req, res){
var email = req.body.email, password = req.body.password;
var user = loginUserPrivate(email, password);
res.send('Found user ' + user);
});
module.exports = {
loginUser: function(email, pass) {
return loginUserPrivate(email, pass);
}
}
As you can see, we’ve added an export function for the login code, so that the unit test can access it. However, there is a problem. Due to the asynchronous nature of node.js, that user value from the private function is not being returned correctly. The private function will return immediately, and not wait for the db call to finish. To fix this, we will use futures, which are also commonly called promises, or deferred objects. To learn more about this concept, this article sums up the concept well.
I’m currently using this futures module, but there are other modules out there, and I encourage you to experiment. If we refactor the application code to use futures, it will look like this:
// login.js
var
express = require('express'),
app = express.createServer(),
// Future object
Future = require('future'),
// Database Config
mongo = require('mongojs'),
mongoStore = require('connect-mongodb'),
db = mongo.connect('dbname',['users']);
// Configuration
app.configure(function(){
...
});
app.listen(3000);
function loginUserPrivate(email, pass) {
var future = new Future(); db.users.find({'email':email,'password':password}).forEach(function(err, user) {
future.deliver(err, user);
});
return future;
}
app.post('/login', function(req, res){
var email = req.body.email, password = req.body.password;
var future = loginUserPrivate(email, password);
future.when (function (error, user) {
res.send('Found user ' + user);
});
});
module.exports = {
loginUser: function(email, pass) {
return loginUserPrivate(email, pass);
}
}
The changes above show one way to get around the async nature of node.js for testing purposes. The private login function will return a future object immediately to the caller. The caller will then essentially subscribe to an event (with future.when()) telling it when the object has data. The caller can then access this data from the callback function.
We can refactor the unit test code to also use futures, and thereby effectively test out the login functionality:
// login-unit.js var login = require('login.js'),
Future = require('future'),
exports.testLogin = function(test){
var future = login.loginUser('email', 'pass');
future.when (function (error, user) {
test.notEqual(user, null, "The user was null!");
test.done()
});
};
Some of you may wonder why we need to use futures / promises at all. Indeed, we could just send in a callback function as an extra parameter to the login function that would get called when the async call is finished. However, there are other benefits to using futures: chaining of objects and default timeouts are just two of the benefits that come with using a futures object.
To recap, what I’ve been trying to show today is that using futures/promises/deferred objects are effective ways to unit test node.js code, despite it’s asynchronous nature. The future objects returned from the private login method allow you to essentially subscribe to an event that tells you when the async call is finished, and passes back the result of that async call. In addition, I believe the above code is more structured and less brittle.
Now that I have a method to unit test my node.js code, I can happily move forward and have some degree of certainty that my code is working correctly.