Testing and Node: Building a Toolset

| Comments

This is part two of a series on testing and Node; check out part one, Why do we test.

Despite Node.js’s relative youth, there’s already a vast ecosystem of tooling surrounding end-to-end application testing. But the bounteous availability of tools isn’t as positive an aspect as it may appear; many tools lack maturity, while others have gone beyond the point of maturity to abandonware. Choosing the right testing toolset when starting a new Node project can be daunting, and can be enough to discourage you from testing in the first place.

Don’t delay. Don’t put off. Follow the advice of the excellent Growing Object-Oriented Software, Guided by Tests and set up your test infrastructure when you first set up your application.

Today, we’re going to go step-by-step through creating a new Node app and its accompanying test toolset. Over the rest of the series, we’ll walk through writing a feature from the outside-in.

Let’s get started. Create a new directory for this project; I created one called testing-and-node. cd in and then npm init. If you haven’t used npm init before, it just asks a series of questions to put together a package.json file for you. Fill in whatever answers you want, but when the script asks for the “test command”, make sure to enter grunt test.

Speaking of which… you do have Grunt installed, right? Grunt is a fantastically powerful build tool for JS projects. While some developers prefer the power and unix-ness of Makefiles, and Rubyists long for Rake’s API, Grunt has a comprehensive ecosystem of tasks that’s growing daily. And since you’re coding in JavaScript, you might as well write your build tasks in JS, too.

If you are new to Grunt, you’ll need to install the cli module for use globally:

1
npm install -g grunt-cli

Next, install a local version of Grunt itself:

1
npm install --save grunt

Great. Grunt relies on the presence of a file called Gruntfile.js to configure and run builds. Let’s set that up now. Open a new Gruntfile.js file in your editor and enter the following:

1
2
3
4
5
6
7
module.exports = function (grunt) {

  grunt.initConfig({

  });

  grunt.registerTask('test', []); };

Go ahead and run npm test. It’s green. Hooray! Yes, it’s true your tests don’t currently do anything, but now you’re free to start setting up the rest of your toolset. Let’s install them in one bunch:

1
npm install --save-dev mocha sinon chai sinon-chai grunt-mocha-test grunt-mocha-webdriver

And if you’re a fan of promises, you might as well install the following right now:

1
2
npm install --save Q
npm install --save-dev chai-as-promised

We’ll also need to install a tool that’s not a Node module: PhantomJS. You can install it with Homebrew if you’ve got it; otherwise, there’s binary installers here. If you already have Phantom installed, just make sure it’s on the latest version.

Let’s go over the selection of each of these tools. There’s certainly other sets of modules that would work equally well, but this is the toolset with which I’m most familiar, and the one for which I’ll advocate.

mocha http://visionmedia.github.io/mocha/

There’s a wide variety of testing frameworks available for Node, from the extraordinarily comprehensive (Jasmine) to the extraordinarily minimal (node-tap). Mocha fits neatly in between these two extremes; its one role is to run tests, and it’s agnostic about output, assertion library, and even interface-style (the only thing Mocha isn’t agnostic about is promise support). Mocha’s best feature is its ease of async use; supply a test with a callback, and it will be run asynchronously.

1
2
3
4
5
6
7
8
9
10
11
12
describe('a sync test', function () {
  it('is run synchronously', function () {
    //assert something synchronous
  });
});

describe('an async test', function () {
  it('is run asynchronously', function (done) {
    //do something async
    done(err, result);
  });
});

sinon http://sinonjs.org/

Since Mocha simply plays the role of test-runner, we need other components to complete our testing toolset. Sinon is a framework-agnostic library that provides spies, stubs, and mocks for JavaScript (Martin Fowler provides a useful explanation of those terms). Spies, stubs, and mocks are integral for properly testing the interfaces through which your application objects communicate; Sinon makes it simple to verify how any function is called, and can even indicate whether a function has been new-ed.

chai chaijs.com

Completing our main set of testing tools is chai, a framework-agnostic assertion library. Node provides its own assertion api directly in core, so it’s not strictly necessary to include a third-party assertion module. But Chai provides a choice of excellent APIs, with error messages that should make the path from red to green much clearer. I’m personally partial to Chai’s expect bdd interface. Another benefit of Chai is that it provides an easy mechanism for adding your own assertion language, which has led to assertion-plugins like sinon-chai and chai-as-promised.

sinon-chai http://chaijs.com/plugins/sinon-chai

I’m a big believer that cleaner APIs lead to better code; while it’s perfectly acceptable to test Sinon object properties directly, the sinon-chai plugin makes your tests even clearer. Instead of:

1
expect(mySpy.calledWith("foo")).to.be.ok;

you can just write:

1
expect(mySpy).to.have.been.calledWith("foo");

Since you’re going to the trouble of using an expressive assertion library, it makes sense to make those assertions as clear as possible.

chai-as-promised http://chaijs.com/plugins/chai-as-promised

For promise-users, chai-as-promised provides the same benefits as sinon-chai: more expressive test assertions. Instead of chaining a promise onto the subject of your test to verify the eventual resolution or rejection, chai-as-promised allows you to use the same chaining-API as the rest of Chai:

1
expect(promiseFn({foo: 'bar'})).to.eventually.deep.equal('foobar')

grunt-mocha-test https://github.com/pghalliday/grunt-mocha-test

Because Mocha can be run on both the client and server, there’s a surprisingly large number of Grunt tasks that use the testing framework; I prefer grunt-mocha-test because of its extreme simplicity. grunt-mocha-test uses the same options hash as Mocha itself; in fact, all it’s doing is firing up processes with which to run Mocha instances. Of course, running those instances properly, and catching any weird exceptions, is harder than it sounds — grunt-mocha-test has figured out those fringe cases so you don’t have to.

grunt-mocha-webdriver https://github.com/jmreidy/grunt-mocha-webdriver

When it comes down to full-stack (aka acceptance or end-to-end) testing, you’ll need a headless browser with which to interact. PhantomJS, which we’ll get to below, provides a headless WebKit browser, but what if you want to run your acceptance tests against more than just a (fake) instance of WebKit? What if you want to know if your application will work on mobile browsers, or legacy versions of IE? That’s where SauceLabs comes into play; it provides an IaaS for testing against any matrix of browsers and devices.

grunt-mocha-webdriver allows you to write and run Mocha tests as you normally would, but with one huge benefit — it automatically inserts a WebDriver/Selenium interface into your tests for you. With grunt-mocha-webdriver, you’ll be able to run a single acceptance test against PhantomJS locally, and then run against a whole suite of browsers on SauceLabs when you deploy to your CI environment.

If all of that sounds incredibly confusing now, it will make more sense in a future post in this series.

PhantomJS http://phantomjs.org/

PhantomJS isn’t a Node module; it’s a headless WebKit browser. Traditionally, the interface through which you’d interact with Phantom would be a JavaScript API, and it was notoriously tricky to setup and integrate into the rest of the testing stack. All that changed with PhantomJS’s 1.8 release, which included Ghost Driver, a pure JavaScript implementation of the WebDriver / Selenium Wire Protocol. With new versions of PhantomJS, you can run:

1
phantomjs --webdriver=4444

That will keep an instance of Phantom running in the background, against which you can run your tests via grunt-mocha-webdriver.

This whole process will be explained more in a later post in this series.



And with that, we have a working testing toolset. We’re ready to build — which we’ll start doing in the next post.

Comments