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
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
If you are new to Grunt, you’ll need to install the cli module for use globally:
Next, install a local version of Grunt itself:
Great. Grunt relies on the presence of a file called
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
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:
And if you’re a fan of promises, you might as well install the following right now:
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.
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
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.
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:
you can just write:
Since you’re going to the trouble of using an expressive assertion library, it makes sense to make those assertions as clear as possible.
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:
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.
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.
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.