Taking Baby Steps with Node.js – BDD Style Unit Tests with Jasmine-Node Sprinkled With Some Should

March 7th, 2011

Here are the links to the previous installments:

  1. Introduction
  2. Threads vs. Events
  3. Using Non-Standard Modules
  4. Debugging with node-inspector
  5. CommonJS and Creating Custom Modules
  6. Node Version Management with n
  7. Taking Baby Steps with Node.js – Implementing Events
  8. I probably don’t have to tell you for the umpteenth time about the importance of TDD and writing unit tests for your code. This is a non-negotiable discipline regardless the platform or programming language you’re using. With JavaScript being a dynamic language, this becomes even more important because you don’t have a compiler to fall back on that takes care of the most general sanity checks.

    Some time ago, during my first explorations of JavaScript, I stumbled upon this simple BDD framework called jasmine. Using jasmine-node, this small specification framework can be made available for Node.js as well. For this blog post I’ll be showing some of the basic usages.

    In order to install jasmine-node, you can either use npm

    npm install jasmine-node

    or use Git to get the latest version of the lib folder that contains the following three JavaScript files:

    image

    Now we can start using jasmine-node. Let’s look at an example of a suite.

    var Customer = require('domain').Customer,
        Order = require('domain').Order,
        OrderItem = require('domain').OrderItem,
        should = require('should');
    
    describe('When making a regular customer preferred', function() {            
    
        var _order = new Order([ new OrderItem(12), new OrderItem(16) ]),
            _totalAmountWithoutDiscount = _order.getTotalAmount();
            _customer = new Customer([ _order ]);    
    
        _customer.makePreferred();
    
        it('should mark the customer as preferred', function() {
            _customer.isPreferred().should.be.true;
        });
    
        it('should apply a ten percent discount to all outstanding orders', function() {
            _order.getTotalAmount().should.equal(_totalAmountWithoutDiscount * 0.9);
        });
    });

    Specifications are organized in suites. A suite is defined by providing a describe() function with a description. It’s also possible to nest suites, although I wouldn’t recommend that as it doesn’t work as one might expect. In this example we set up a regular customer which we then turn into a preferred customer. A specification is defined by providing an it() function with a description. For the actual verifications I opted for using should.js which provides BDD style assertions that are test framework agnostic instead of the matchers built into Jasmine.

    // Built-in matchers
    expect(_customer.isPreferred()).toBeTruthy();
    expect(_order.getTotalAmount()).toEqual(_totalAmountWithoutDiscount * 0.9));
    
    // Should.js
    _customer.isPreferred().should.be.true;
    _order.getTotalAmount().should.equal(_totalAmountWithoutDiscount * 0.9);

    I really like the syntax provided by should.js, but that’s just my personal opinion of course.

    Note that suites are just plain old JavaScript functions that are executed only once. This therefore means that our setup code is also executed only once. I particularly like this as it prevents context betrayal and forces the specifications to just observe the outcome.

    However, it’s also possible to provide a function that runs before each specification.

    var Customer = require('domain').Customer,
        Order = require('domain').Order,
        OrderItem = require('domain').OrderItem,
        should = require('should');
    
    describe('When making a regular customer preferred', function() {
        var _order, _totalAmountWithoutDiscount, _customer;
    
        beforeEach(function() {
             _order = new Order([ new OrderItem(12), new OrderItem(16) ]),
             _totalAmountWithoutDiscount = _order.getTotalAmount();
             _customer = new Customer([ _order ]);    
    
            _customer.makePreferred();
        });
    
        it('should mark the customer as preferred', function() {
            _customer.isPreferred().should.be.true;
        });
    
        it('should apply a ten percent discount to all outstanding orders', function() {
            _order.getTotalAmount().should.equal(_totalAmountWithoutDiscount * 0.9);
        });
    });

    Now, we’ll need to provide some plumbing in order to execute these specifications using Node.js. We need to provide a small script that picks up all specifications for a particular folder and feed these to jasmine for executing them.

    var jasmine = require('jasmine-node');
    var sys = require('sys');
    
    for(var key in jasmine) {
      global[key] = jasmine[key];
    }
    
    var isVerbose = true;
    var showColors = true;
    
    process.argv.forEach(function(arg){
        switch(arg) {
              case '--color': showColors = true; break;
              case '--noColor': showColors = false; break;
              case '--verbose': isVerbose = true; break;
          }
    });
    
    jasmine.executeSpecsInFolder(__dirname + '/specifications', function(runner, log){
      if (runner.results().failedCount == 0) {
        process.exit(0);
      }
      else {
        process.exit(1);
      }
    }, isVerbose, showColors);

All our specifications reside in the specifications folder which are executed by jasmine when we run this script (that we named specs.js).

image

There you go. I’m also evaluating some other stuff regarding TDD and BDD for JavaScript and Node.js. I’ll be blogging about that as well in the near future.

Until next time.

  • PepeMax

    hey, the cli says assertions 0, is that because you are using should instead of the jasmine matchers?

    • Anonymous

      Yes

  • Alan Huffman

    Mind sharing the entire projects code? Including your domain?

    • Anonymous

      I’ll post the code on GitHub soon.