Wednesday, May 19, 2010

Stubbing Node Code for TDD

I've been furiously hacking with Node to build Campaign Narrative, a website for hosting and playing role-playing games online. I'm storing data in MongoDB, so I'm building a small library to use the Repository pattern on top of it. Once I had something that worked, I decided to toss my spike code and do it for real (via TDD). That's when I realized I had a problem.

The Problem
My repository library uses the node-mongodb-native library. I need to stub lots of stuff in node-mongodb-native to TDD my repository code. I tried several approaches: changing and extending prototypes, global redefinition, unshifting a folder of stubs into the require paths; none of it worked (or didn't work in a way that made me happy). Because I'd been hacking Ruby very recently, I was approaching the problem from that perspective. Then I wondered, how would I handle this in .NET? I'd use dependency injection.

My Solution
I decided to use an (admittedly very crude) form of constructor injection so I could stub my dependencies. Here is an excerpt from my mongodb_repository.js library:
require("lib/prototypes");
exports.MongoDBRepository = function(config, collectionName, deps) {
  var config = config || {};
  config.merge({
    host: "127.0.0.1",
    port: 27017
  });
  
  deps = deps || {
    Server: require('mongodb/connection').Server,
    Db: require('mongodb/db').Db
  };
  
  this.collectionName = collectionName;
  this.server = new deps.Server(config.host, config.port, {});
  this.db = new deps.Db(config.dbName, this.server)
  
  this.db.open(function(){});
};
You'll notice the third argument of my constructor function, deps. By wrapping all of my dependencies in this deps object (instead of the typical, module-level var dep = require("dep"), I can easily pass stubs in for the dependencies in my test code.

Here is an excerpt of application code that creates a repository object, without passing in deps:
var repositoryConfig = {
  host:   process.env['mongodb-host']   || "127.0.0.1",
  port:   process.env['mongodb-port']   || 27017,
  dbName: process.env['mongodb-name']   || "development" 
};

var accountRepo = new Repository(repositoryConfig, "accounts");
And here is an excerpt from my spec, which passes in stubs for deps:
the("Repository constructor function", function(will) {

  will("use a default host of 127.0.0.1 if no host is provided", function (done) {
    var stubs = {
      Server: function(host, port, options) {
        assert.equal("127.0.0.1", host);
        done();
      },
      Db: function(dbName, server) {}
    };
    stubs.Db.prototype.open = function(callback){ };    
    new Repository(null, "theCollection", stubs);
  });

});
One of the things I really like about this approach is that my tests are that much more intention-revealing, in that it's very clear what is being stubbed. I know it's a small, simple thing, but it makes me happy. I also like that I don't need any special stubbing library.

This approach works just as well with Node's built-in modules. For example, I stub the sys module for the puts function in my Willful spec'ing framework.

How do you stub with Node? Please share your own approach with me!

Cheers,
Drew (twitter.com/anglicangeek)

4 comments:

  1. I haven't tried it with node, yet, but I've been having good luck stubbing (and spying) with my spies.js framework. (shameless plug). :) I'm working with node now, so I'll see how it goes with the modules and what might need to be altered with it.

    http://github.com/coreyhaines/spies.js

    ReplyDelete
  2. Thanks for the note, Corey.

    Looking at spies.js, it's not clear to me whether it would work with Node's (really CommonJS') modules specification. It looks like (after an admittedly brief glance) you're always stubbing on objects you create and pass to the code-to-test. With modules, you won't be creating the objects in test code, unless you broker with some sort of dependency injection. I'm used to Ruby's meta-prog, which allowed me to skip DI in my test code, but I couldn't find a way to do similar things in Node. And, now, I actually like the locality and terseness I have with the DI approach.

    One way in which spies.js might be nice is keeping the assertions out of the stubs. With Node, because so much of the code relies on callbacks, I've found myself peppering my stubs with assertions (and with the finished signal, since the callbacks are often asynchronous). At first, the purist in me was appalled by this, but now I'm actually starting to like it. It's very readable, compact, and I haven't missed a stubbing library, yet.

    But, using a spy-oriented stubbing framework is definitely a good approach to keep the assertions in a more preferable 3A pattern.

    ReplyDelete
  3. The spy approach boiled naturally out of the way my js testing was going. I like it a lot more than a mock-style one.

    Yeah, the common.js module system is a bit different. I'll be looking to use spies.js when I start doing more isolation testing in node, so I'll see what it will take to change it (if it is worth it).

    For now, I haven't started testing the web stuff, which is where I'll be doing a lot of isolation, I'm only testing more straight-forward stuff, as you can see in my repo going through the 'Purely Functional Data Structures' book: http://vurl.me/QSQ

    ReplyDelete
  4. From another angle, the testing that I've been doing on other, webpage-based stuff, is using heavy DI. This simulates better the node experience, especially as it relates to the eventing mechanism. Modules are still an interesting one, though. I wonder if it would be worth putting parameter-based stubs on require(). Hmm....

    ReplyDelete