3 Quick Tips for Writing Tests in Node.Js (after some rambling)

You are probably already aware of Node.JS, unless you've been living under a rock for the past year. It's a pretty ingenious project combining the extremely fast Google V8 JavaScript engine with a highly-optimized cross-platform asynchronous networking library (libuv). The next two paragraphs will be some rambling opinions on Node.JS coming from a Python and C hacker. Then we shall get down to writing tests.

Node.JS makes it possible to write very efficient async network demons in a higher-level language than C without taking a huge performance hit. V8 is extremely fast and libuv and the NodeJS core library have been heavily optimized for minimal number of system calls. Also, since absolutely everything in the ecosystem is asynchronous from the start, you don't have to worry about things like how you can effectively talk to your database because your MySQL driver does blocking IO. Node.JS is a good fit for people who would like to leverage asynchronous IO in their program but don't want the headaches of programming in C or using a slower language like Python or Ruby which also carry a legacy of synchronous-only library code.

While I originally really disliked JavaScript as a language before learning Node.JS, it has since become a lot more enjoyable. I have found its support for object literals, closures and anonymous functions to be a great fit for async network programming. Furthermore the lightweight syntax and CommonJS module system have grown on me. Finally, I am loving the philosophy of a minimalist standard library married with an awesome, Github-integrated, local-by-default package system, npm. I also recommend using the underscore library to anybody who finds themselves missing the list and set operations from other languages.

Testing in Node.JS

Node.JS is very new, still maturing and pre 1.0 release. I initially found it hard to know where to begin with writing tests. Which test runner is standard? How do you do assertions? What about dependency injection / mocking? Are there easy ways to set up functional (ie full stack) tests?

Tip 1 - Choosing a Test Runner

It turns out that there are a whole bunch of JavaScript and Node.JS-specific test runners/frameworks. The major ones I'm aware of:

  • Nodeunit - Simple, lightweight test runner for Node and browser. Supports setup and teardown and all the basic stuff you'd expect.
  • Mocha - Similar to Nodeunit, few more bells and whistles (like a test-creation API) along with a zillion output formatters.
  • Vows - BDD framework which I couldn't really get into personally but is clearly quite powerful and popular.

I ended up choosing Mocha and haven't regretted it. It is easy to use from the command line (reflects test status via exit code), I can use regular expressions to select narrower sets of tests and it has an API for defining tests programmatically along with machine-readable output formats. You can use a few different vocabularies (BDD, TDD, etc) to write your tests. It reminds me a bit of Nose from the Python world, but not quite as full-featured (yet). Oh, and Mocha makes it easy to test asynchronous code, too. Just remember to use the done() function, otherwise you can end up with strange bugs in your tests :-)

Tip 2 - Choosing an Assertion Library for Node.JS

In Python, the standard library contains an assertion library and often the test runner will provide some more wrappers, for example either nose.tools or unittest.TestCase. In Node, the standard library contains some basic assert functions. If you want more than that (e.g. some convenience wrappers) you'll need to look for an additional assertion library. It doesn't seem like the test runner people are in the business of providing assertion shortcuts at the moment.

I ended up going with should.js which patches the Object prototype. This enables you to write code like:

// copied from example at https://github.com/visionmedia/should.js
var user = {
      name: 'tj'
    , pets: ['tobi', 'loki', 'jane', 'bandit']
};
user.should.have.property('name', 'tj');
user.should.have.property('pets').with.lengthOf(4);

To be honest I'm not sure I like this style because it is a bit wordy and doesn't work with null or undefined. To assert that a variable is null or undefined, you have to use something else. However should.js provides some useful shortcuts so I've stuck with it.

I also came across Chai which offers the same "should" / BDD-type stuff that should.js offers, in addition to having a couple of other modes, including "assert"-style. The Chai assert-style adds a few nice convenience methods over the Node.JS stdlib assert module.

Next time I might just use the standard Node assert module or try Chai's convenience wrappers. I am not feeling amazing benefits to the should.js BDD style right now.

Tip 3 - Mocking / Stubbing / Dependency Injection in Node.JS

Let's imagine you have written a module which exports a number of functions. Those functions depend on another module which you use for communicating with an external resource - a good example is a database. Consider the following two modules:

// database.js - functions for talking to persistence store
exports.insert = function(key, value, cb) {
  // ... implementation ...
}
exports.query = function(key, cb) {
  // ... implementation ...
}

// user.js - functions to manage users of the system
var database = require('./database.js');
exports.get_user = function(username, cb) {
  database.query("user_" + username, function(err, user) {
      if (err) throw err;
      var res = {};
      // ... do some additional processing then call cb() ...
  });
}

You want to test user.js without actually talking to a real database. You want to do this to avoid the performance penalty and additional complexity involved in actually testing against a real database. Note that you may also choose to have further integration/functional/insert-jargon-here tests which do actually talk to a real database, but that isn't the case I'm talking about here.

People use different terms to describe the act of replacing the implementations of the "database.js" functions when under test. Mocking, stubbing, sandboxing, dependency injection - all terms I have heard people use. I am used to using the Python Mock module for this so I tend to use "mock". I don't think it really matters.

I searched around for a few different mocking libraries, but I ended up finding things which would build mock objects but wouldn't actually handle the cross-module dependency injection problem. For example, Sinon.js is great for building fake objects and then asserting that methods have been called a specific number of times or with particular arguments. However, it won't enable you to stub out module dependencies.

Eventually I stumbled upon @felixge's node-sandboxed-module which does exactly what I was looking for. It uses the Node.js VM module to run a module in a totally new V8 context, and then load in replacement required modules. For example, I have a module which communicates with the Heroku HTTP API. I wish to mock out actual HTTP responses from the API so that my tests run faster and I avoid being IP banned for exceeding an API call limit. My Heroku module lives in a file called "heroku.js" and it uses @mikeal's Request HTTP client library. So I use node-sandboxed-module to inject a Mock-Request instance to fake out the server response:

it('should correctly set the SSH key and persist details in the user object',
function(done) {
  // Mock the real user object with an object literal
  var user_obj = {heroku: [], save: function(cb) {cb(null, this)}};
  // Fake out the HTTP response: https://github.com/nodejitsu/mock-request
  var monkey_request = mockrequest.mock({
    protocol: "https",
    host: "api.heroku.com"
  })
    .post('/user/keys?')
    .respond(200)
    .run();
    // Make the result of "require('request')" in the heroku module under
    // test be monkey_request
  var heroku = sandboxed_module.require('../heroku.js', {
    requires: {'request':monkey_request}
  });

  // Call the function under test and assert that the expected
  // side-effects have been realized.
  heroku.setup_account_integration(user_obj, TEST_USER_API_KEY,
    function(err, user_obj) {
    user_obj.heroku[0].api_key.should.eql(TEST_USER_API_KEY);
    user_obj.heroku[0].pubkey.length.should.be.above(0);
    user_obj.heroku[0].privkey.length.should.be.above(0);
    user_obj.heroku[0].account_id.should.match(/^.*@.*$/);
    done();
  });
});

Conclusion

Compared to other environments (Rails, Pyramid, Django, etc) there is a bit more leg work to getting your tests started with Node.JS. Other environments offer a bit more of a unified, pre-baked testing setup. However, it is perfectly feasable to write a very comprehensive test suite for your application, employing unit tests, integration tests (optionally with mocks) and fullblown, full-stack functional tests.

I'm sure that in the relatively near future, people in the Node community will begin settling on tools and libraries and things will consolidate a bit more. This should make it easier for newcomers like myself to know where to start. Tests FTW!

Final note: Make sure your application supports npm test :-)

Niall O'Higgins is an author and software developer. He wrote the O'Reilly book MongoDB and Python. He also develops Strider Open Source Continuous Deployment and offers full-stack consulting services at FrozenRidge.co.

Read and Post Comments