Testing in Node.js with Mocha

來源:互聯網
上載者:User

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 primarily in 3 different directories off the project root, 'controllers', 'models' and 'middleware'. I would need to instrument all 3 directories, and then get my tests to be smart about whether to use the instrumented vs. non-instrumented code.

I didn't see a very clean way to do this, so I started poking around how Express and other projects handled this. I found that it becomes very simple if you organize all of your source code under a single directory (i.e. lib) and export it all as a single module. Then you can create an index.js file (just like Express) at the root of the project that simply exports either /lib or /lib-cov based on the some environment variable.

Our project was laid out like this:

config/
controllers/
middleware/
models/
public/
routes/
test/
views/
app.js
package.json
MakeFile
After rearranging things, it now looks somewhat like this:

public/
lib/
  config/
  controllers/
  middleware/
  models/
  instinct.js
routes/
test/
views/
app.js
packageon.json
MakeFile
index.js
I had to add instinct.js that consolidated all my models, controllers, middleware and exported them from one place. Then elsewhere in my project anytime I needed a model, controller or middleware I just required that main instinct module like so:

var instinct = require('./instinct');
Then I added an index.js file at the project root that exports either lib/instinct or lib-cov/instinct depending on the INSTINCT_COV environment variable (which we'll set in our MakeFile):

module.exports = process.env.INSTINCT_COV
  ? require('./lib-cov/instinct')
  : require('./lib/instinct')
Now when node-jscoverage instruments my lib/ code to lib-cov/, if my environment variable INSTINCT_COV is set to true, any require to './instinct' will load the instrumented code.

Now with my project organized, I just had to modify my MakeFile, again borrowing from the MakeFile in Express:

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

test-cov: lib-cov
  @INSTINCT_COV=1 $(MAKE) test REPORTER=html-cov > public/coverage.html

lib-cov:
  @jscoverage lib lib-cov

.PHONY: test test-w
And that was it, I was able to generate a coverage report by running:

make test-cov
Next Steps
After running with Mocha on the server for a bit, I was really impressed with how nice it made testing, and confirmed that I definitely wanted to migrate our client-side tests too (which I have since completed & plan to write about in another post).

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.