Choices and More Choices

The good news when getting off the ground with NodeJS: if you like testing, there are many interesting and solid choices. The ones I looked at are:

  • Vows. A very interesting asynchronous test framework with beautiful documentation
  • Mocha. A lively, capable testing tool that I ended up choosing.

I started off with Mocha - it came highly recommended and it supports a wide variety of options. You can write your tests in Javascript or CoffeeScript, and the layout is incredibly readable - it even has a file watcher and it reruns when you change select files:

You can choose other layouts if you like - there are plenty of options. I went with "--spec" as I like it. You can put all of your options in a single file called "mocha.opts" - which should go in your "/test" directory. Here's mine:

--require should
-R spec
--ui bdd

Mocha suggests strongly that you put your tests into a "/tests" directory right off your root. So that's what I did. I wrote them in CoffeeScript and everything was going swell - until I realized that the tests were passing even when I knew they shouldn't be passing.

This is something you run into with Javascript: the asynchronous nature of the language means that sometimes the test will finish before the assertion is made - which is a false positive. Mocha has a way around this, which I'll discuss in a minute - but for some reason it just wasn't working right.

So I decided to try Vows. It's beautiful and the output is amazing. I ran into a small issue with how the asynchronous stuff is supposed to handle multiple callbacks (more on that below) - turns out it doesn't at all (which seems silly). I checked out the site on Github and there are numerous open issues with a lot of people getting stuck.Given this, I did what I normally do when I get stuck on a Javascript issue: I called Batman.

The first thing Dave did, upon reviewing my mess, to ask me

Would you mind getting rid of the CoffeeScript so I can read this?

A small tangent here: I decided to write the app in straight up Javascript because that's just how I think about Javascript. If I use CoffeeScript, I start writing Ruby. That's me, and maybe I'm nuts. For tests, however, I love the Ruby style syntax of CoffeeScript - very readable and that's what you want.

Anyway I dropped the CoffeeScript and started over with Javascript. And guess what started working?

Everything. Go figure. It turns out there are number of "weird" things when the test runner transforms the CoffeeScript - let's just leave it that it behaves a lot nicer if you use straight up JS.

Installs

The first thing to do is install Mocha and the goodies I'll need to work with today...

npm install -g mocha
npm install should
npm install mongoose

Once again - that -g is very important when install Mocha because it puts it into your global modules. You need this because you're going to execute mocha from your command line. "should" will simply allow me to write some groovy RSPec'y syntax and Mongoose is how I'll connect to MongoDB (more on that later).

Writing a test is pretty simple:

var customer = require('../lib/customers');
describe("Customers", function(){  
  it("retrieves by email", function(done){    
    customer.findByEmail('test@test.com', function(doc){      
      doc.email.should.equal('test@test.com');       
      done();    
    });  
  });
});

I'll talk about the "require" statement in just a bit.Notice the "done()" call there? Mocha is deferring execution of the rest of the tests until my callback is done. We tell it when we're done by... well you get it. But a simple test doens't convey much - so now I need to go sideways and talk about my Customer.

Customer

I need to define my model and also pick which tool I want to use to interact with MongoDB. I don't like abstractions much - but I also don't like managing connections states and diddling with the node-mongodb-native driver's wordy API.

The good news is that Mongoose, a great ODM for Node/Mongo allows you to sidestep the abstraction readily - so I'll make life easy on myself and do that.

I want to put this all into a Customer module so it's nice and encapsulated - so I'll add a file to my "/lib" directory called "customers" (lib is a directory that Express installs for you where you can put your modules) and I'll plumb that file with using a Revealing Module pattern that Dave has beat into my head:

var Customer = function(){  
  var _login = function(email, password, callback){    
    //...  
  }  
  var _register = function(email, password, confirm, callback){    
    //...  
  }  
  return {    
    login    : _login,    
    register : _register  
  }
}();
module.exports = Customer;

This pattern can look a bit noisy - but it's a good habit to wrap your code in a nice, tight little scope ball - and you get the added benefit of keeping things private. It takes a bit to understand what's happening if you don't know Javascript well - but basically this is a "self-invoking" function that executes when referenced.

All the private and internal stuff stays internal and private, and you can explicitly decide what you want to share using the return statement. Yes, it's a tad noisy but Batman told me to use it... so that's what I do.

The special sauce here is Node's "module.exports" feature. It's a place where your modules get to hang and avoid namespace collisions with other modules. To use our new module we would do this:

var customer = require('./lib/customers'); //customers.js is the name of the file
customer.login("test@test.com","password",function(result){  
  //login stuff...
});

The require statement loads the file and appends whatever functionality you dicate onto "exports" - and then pops that into the variable you decide. This keeps the global namespace tidy and modular functionality intact.

Now, I could very well do something like this as well:

module.exports.login = function(email,password,callback){  
  //...
}
module.exports.register = function(email, password, confirm, callback){ 
  //...
}
function whoops(){  
  console.log("Hey I'm GLOBAL YO");
}

And I would invoke and use it in the exact same way. A lot of people do this but I tend to like the idea of only one exports path - it seems a lot more readable and clean but at the same time... but as you can tell with the last line - it's easy to goof up.

Using Mongoose

I talked to Dave Ward for about 90 minutes today, asking for him to proof what I'm doing as far as design goes. My goal is to wrap up a Customer API in my module - exposing only what's needed for the app to run.

Originally I was just going to use Mongoose directly but as Dave and I talked about it - it seemed like a better path to keep Mongoose under wraps. Let's put this together:

var Customer = function(){  
  var mongoose = require('mongoose');  
  var Schema = require('mongoose').Schema;  //bummer - have to declare this up-front  
  var customerSchema = new Schema({    
    id    : Number,    
    email : {type : String, index: { unique: true, required : true } },    
    first : String,    
    last  : String,    
    crypted_password :String,    
    auth_token : String,     
    invoices : [{type: Schema.ObjectId, ref : 'Invoice'}]  
  });  
  //the model uses the schema declaration  
  var _model = mongoose.model('customers', customerSchema);  
  var _findByEmail = function(email, success, fail){    
    _model.findOne({email:email}, function(e, doc){      
      if(e){        
        fail(e)      
      }else{       
       success(doc);      
     }    
   });
  }  
  return {    
    schema : customerSchema,    
    model : _model,    
    findByEmail : _findByEmail,  
  }
}();

The bummer here is that we need to declare our Schema up-front... which is weird as Mongo is schema-less and it's a strength of using MongoDB. However Mongoose, the ODM tool I'm using, needs to know about your data so it can effectively query it. So I think of this less as a Schema, and more of "what's in there". If I need to add custom stuff I can. But I don't need that - not yet. Right now I want to get back to testing things.

Mocha, Like RSpec Sorta

In short: I want to use a test database like I do with Rails. I want to add a test record to test against, and I want to clear it out when I'm done with each test. Thankfully - with Mocha this is easy:

var mongoose = require("mongoose");
var customer = require("../lib/customers");
mongoose.connect('mongodb://localhost/tekpub_test');  
describe("Customers", function(){  
  //holds a customer to use in the each test  
  var currentCustomer = null;  
  beforeEach(function(done){    
    //add some test data    
    customer.register("test@test.com", "password", "password", function(doc){      
      currentCustomer = doc;      
      done();    
    });  
  });    
  afterEach(function(done){    
    //delete all the customer records    
    customer.model.remove({}, function() {      
      done();    
    });  
  });  
  //tests...  
});

beforeEach and afterEach are fired before, and after, each test run. It might seem noisy to do this - but it's actually quite fast. I know many people don't like this kind of thing (testing against the database) and I can dig it. I'll get into mocking in a later post.

For now, I can test my app thus:

var mongoose = require("mongoose");
var customer = require("../lib/customers");
//tell Mongoose to use a different DB - created on the fly
mongoose.connect('mongodb://localhost/tekpub_test');  
describe("Customers", function(){  
  var currentCustomer = null;  

  beforeEach(function(done){    
    //add some test data    
    customer.register("test@test.com", "password", "password", function(doc){      
      currentCustomer = doc;      
      done();    
    });  
  });  

  afterEach(function(done){    
    customer.model.remove({}, function() {      
      done();    
    });  
  });

  it("registers a new customer", function(done){    
    customer.register("test2@test.com", "password", "password", function(doc){      
      doc.email.should.equal("test2@test.com");      
      doc.crypted_password.should.not.equal("password");      
      done();    
    }, function(message){      
      message.should.equal(null);      
      done();    
    }); 
  }); 

  it("retrieves by email", function(done){    
    customer.findByEmail(currentCustomer.email, function(doc){      
      doc.email.should.equal("test@test.com");       
      done();    
    });  
  });  

  it("retrieves by token", function(done){    
    customer.findByToken(currentCustomer.auth_token, function(doc){      
      doc.email.should.equal("test@test.com");      
      done();    
    });  
  });  

  it("authenticates and returns customer with valid login", function(done){    
    customer.authenticate(currentCustomer.email, "password", function(customer){      
      customer.email.should.equal("test@test.com");      
      done();    
    }, function(){      
      throw("oops");      
      done();    
    });  
  });  

  it("authenticates and returns fail with invalid login", function(done){    
    customer.authenticate(currentCustomer.email, "liar", function(customer){      
      throw("This shouldn't happen");    
    }, function(){      
      done();    
    });  
  });
});

And it works...

If I'm feeling frisky I can also tell it to watch the files in my project, and run whenever I save one:

mocha -w

There's more, obviously, to show about my Customer model... and I'm going to dive into that in my next post...

Update

For fun, here's the CoffeeScript that I just threw together for the tests.

mongoose = require("mongoose")
customer = require("../lib/customers")
assert = require("assert")
mongoose.connect('mongodb://localhost/tekpub_test')  

describe "customers", ->  
  currentCustomer = null;    

  before (done)->    
    customer.model.remove {}, ->      
      done()  

  beforeEach (done)->    
    #add some test data    
    customer.register "test@test.com", "password", "password", (doc) ->      
      currentCustomer = doc      
      done()     

  afterEach (done)->    
    customer.model.remove {}, ->      
      done()  

  it "registers a new customer", (done) ->    
    customer.register "test2@test.com", "password", "password", (doc)->        
      doc.email.should.equal "test2@test.com"        
      doc.crypted_password.should.not.equal "password"        
      done()      
      , (message) ->        
        message.should.equal null  

  it "retrieves by email", (done) ->    
    customer.findByEmail currentCustomer.email, (doc) ->      
      doc.email.should.equal "test@test.com"      
      done()  

  it "retrieves by token", (done)->    
    customer.findByToken currentCustomer.auth_token, (doc) ->      
      doc.email.should.equal "test@test.com"      
      done()  

  it "authenticates and returns customer with valid login", (done) ->    
    customer.authenticate currentCustomer.email, "password", (customer) ->      
      customer.email.should.equal "test@test.com"      
        done()    
      , ->      
          throw "oops"      
        done()  

  it "authenticates and returns fail with invalid login", (done) ->    
    customer.authenticate currentCustomer.email, "liar", (customer) ->      
      throw "This shouldn't happen", ->      
        done()