Testing in Node.js with Mocha
I recently migrated all of our server side Node tests at Instinct from Vows to Mocha.
I've been around the block with testing in Node, unable to find an approach that I really liked, until I started using Mocha. The elegant way it does async along with familiar BDD style syntax is really enjoyable to work with. It's made writing/maintaining our tests something I don't run from anymore.
Two main reasons I made the switch:
Syntax: The bulk of our code for Instinct is on the client-side where we were usingJasmine. Personally, I prefer the BDD style of Jasmine with nested closures over Vows' syntax. I knew getting all our Javascript tests consistently using the same syntax would be a big win and keep me dedicated to writing and maintaining our tests.
Better Async Testing on the Client + Server: The way Mocha does async testing is very elegant. I was becoming frustrated with Jasmine's approach to async testing, which is basically to force it to run synchronously with timers. I wanted to first try Mocha on the server, and then if it went well I knew I could somewhat easily migrate my client-side tests to Mocha as well.
Plus we had to do a big re-factor/rewrite of several pieces on the server anyway, so it seemed like a good time to make the switch.
First Steps
First you need to install Mocha. I justed added it to our package.json and installed it locally under our project. Or you can just install it manually:
npm install mocha
We were using Vows for both unit and integration tests. So after installing things, I wanted to first get our unit tests working. This was literally just a matter of updating the syntax of our tests.
From something like this in Vows:
vows.describe('User Model').addBatch({
'Create a new user' : {
topic: function(){
User.create({ username: 'test' }, this.callback);
},
'should have a username': function(doc){
assert.equal( 'test', doc.username );
}
}
}).export(module);
To this in Mocha:
describe('Creating a new User',function(){
var user;
before(function(done){
User.create({ username: 'test' , function(err,u){
user = u;
done();
});
});
it('should have a username',function(){
user.should.have.property('username','test');
});
});
I opted to use the 'should' syntax, which is a separate module you need to install & require in your tests.
Running the tests
To run the tests, I followed TJ's example of setting up a MakeFile in the root of the project, like this:
REPORTER = dot
test:
@NODE_ENV=test ./node_modules/.bin/mocha \
--reporter $(REPORTER) \
test-w:
@NODE_ENV=test ./node_modules/.bin/mocha \
--reporter $(REPORTER) \
--growl \
--watch
.PHONY: test test-w
Then you can just run your tests with:
$ make test
By default mocha will run everything in /tests off of your main project. I've been following the same organization for my test files as TJ uses in Express, where all the tests are in that main /tests directory and the filenames indicate the namespacing.
I opted for the 'dot' reporting output, but there's tons of options. And it's even pretty easy to fork and create your own if you're looking for something specific in your test output.
Auto-running the tests
Just like autotest in Rails, you can setup Mocha to run automatically whenever one of your files changes. That was the purpose of the test-w target in the MakeFile above. So now I could just make my tests run continuously in the background by running:
make test-w
To make it work on Lion with growl notifications, I needed to install growlnotify and thennode-growl.
Setting up 'npm test'
The next thing I did was modify our package.json file so that it runs our tests whenever someone executes 'npm test' in the project.
Everyone in the Node community seems to have their own methods for running tests, but one thing that does seem like it's standardizing is that the command 'npm test' will run the tests for a node project. You can do this just by adding it under the 'scripts' attribute in your package.json file:
{
"name": "instinct",
"version": "0.0.1",
"dependencies": {},
"scripts": {
"test": "make test"
}
}
Now you can also run your tests via npm:
npm test
Migrating our Integration Tests
My next step was getting the integration tests running in Mocha. Our node server is essentially just a json REST API, so our integration tests are largely HTTP requests that test response codes + the json of the response body.
Vows didn't have any built-in support for HTTP testing, so I created my own helper methods to do this. I was a little worried I'd have to rewrite these for Mocha as it doesn't seem to have anything built-in to help with Integration tests.
After poking through the tests for Express however, I found that TJ had a support lib that he was using to test HTTP requests/responses. I was able to create a modified versionthat gave me everything I had in Vows.
And then I was able to write nice looking HTTP tests like this:
var app = require('../app')
, http = require('support/http');
describe('User API',function(){
before(function(done){
http.createServer(app,done);
});
it('GET /users should return 200',function(done){
request()
.get('/users')
.expect(200,done);
});
it('POST /users should return 200',function(done){
request()
.post('/users')
.set('Content-Type','application/json')
.write(JSON.stringify({ username: 'test', password: 'pass' }))
.expect(200,done);
});
});
'app' is your main Express app and 'http' is the support lib.
Generating a Coverage Report
The last piece of the puzzle was getting a test coverage report. This wasn't something I had setup with Vows, but now seemed like a good time to do it as Mocha's coverage reports looked pretty sweet.
The first thing I did was install node-jscoverage.
To generate a coverage report you need to first instrument your code so that when it runs it can track what lines have been called and which haven't. This is what node-jscoverage does, it will essentially create a copy of every file you tell it to. You then need to make your tests use the instrumented code instead of the normal code.
This is when I realized I had a problem. My source code was prim
补充:web前端 , JavaScript ,