Testing Node.js Code with Mocha and Chai

Writing unit tests is one of those things a lot of people forget to do or just avoid altogether, but when you have them they're lifesavers.

Test-driven development, which means you write your tests before your code, is a great goal to strive for, but takes discipline and planning when you're programming. To make this whole process a lot easier, you need easy-to-use and powerful testing and assertion frameworks, which is exactly what Mocha and Chai are.

In this article I'll introduce you to these two libraries and show you how to use them together to quickly create readable and functional unit tests.

Chai

Chai is an assertion library that provides both the BDD and TDD styles of programming for testing your code in any testing framework.

Throughout this article, we'll focus on the BDD style using Chai's expect interface.

expect uses a more natural language API to write your assertions, which will make your tests easier to write and improve upon later on down the road. This is done by chaining together getters to create and execute the assertion, making it easier to translate requirements in to code:

var user = {name: 'Scott'};

// Requirement: The object 'user' should have the property 'name'

expect(user).to.have.property('name');  

A few more examples of these getters are:

  • to
  • be
  • is
  • and
  • has
  • have

Quite a few of these getters can be chained together and used with assertion methods like true, ok, exist, and empty to create some complex assertions in just one line. A few examples:

"use strict";

var expect = require('chai').expect;

// Simple assertions
expect({}).to.exist;  
expect(26).to.equal(26);  
expect(false).to.be.false;  
expect('hello').to.be.string;

// Modifiers ('not')
expect([1, 2, 3]).to.not.be.empty;

// Complex chains
expect([1, 2, 3]).to.have.length.of.at.least(3);  

A full list of the available methods can be found here.

You might also want to check out the list of available plugins for Chai. These make it much easier to test more complex features.

Take chai-http for example, which is a plugin that helps you test server routes.

"use strict";

var chai = require('chai');  
var chaiHttp = require('chai-http');

chai.use(chaiHttp);

chai.request(app)  
    .put('/api/auth')
    .send({username: 'scott@stackabuse.com', passsword: 'abc123'})
    .end(function(err, res) {
        expect(err).to.be.null;
        expect(res).to.have.status(200);
    });

Mocha

Mocha is a testing framework for Node.js that gives you the flexibility to run asynchronous (or synchronous) code serially. Any uncaught exceptions are shown alongside the test case in which it was thrown, making it easy to identify exactly what failed and why.

To use Mocha, I'd suggest you install it globally with npm:

$ npm install mocha -g

You'll want it to be a global install since the mocha command is used to run the tests for the project in your local directory.

Creating test cases are easy using the describe() method. describe() is used to bring structure to your tests by grouping other describe() calls and it() methods together, which is where the actual tests are located. This is probably best described with an example:

"use strict";

var expect = require('chai').expect;

describe('Math', function() {  
    describe('#abs()', function() {
        it('should return positive value of given negative number', function() {
            expect(Math.abs(-5)).to.be.equal(5);
        });
    });
});

Note that with Mocha tests you don't need to require() any of the Mocha methods. These methods are provided globally when run with the mocha command.

In order to run these tests, save your file and use the mocha command:

$ mocha .


  Math
    #abs()
      ✓ should return positive value of given number 


  1 passing (9ms)

The output is a breakdown of the tests that ran and their results. Notice how the nested describe() calls carry over to the results output. It's useful to have all of the tests for a given method or feature nested together.

These methods are the basis for the Mocha testing framework. Use them to compose and organize your tests however you like. We'll see one example of this in the next section.

Writing Tests

The recommended way to organize your tests within your project is to put all of them in their own /test directory. By default, Mocha checks for unit tests using the globs ./test/*.js and ./test/*.coffee. From there, it will load and execute any file that calls the describe() method.

Personally, I like to use the suffix .test.js for the source files that actually contain Mocha tests. So an example directory structure could look like this:

├── package.json
├── lib
│   ├── db.js
│   ├── models.js
│   └── util.js
└── test
    ├── db.test.js
    ├── models.test.js
    ├── util.test.js
    └── util.js

util.js wouldn't contain any actual unit tests, just utility functions to help with testing.

You can use whatever structure makes sense to you (which is the nice thing about Mocha), but this has worked well for me in the past.

When it comes to actually writing the tests, it helps to organize them using the describe() methods. You can organize them by feature, function, file, or something else.

Expanding on our example from the previous section, we'll choose to organize tests by function, which results in something that looks like this:

"use strict";

var expect = require('chai').expect;

describe('Math', function() {  
    describe('#abs()', function() {
        it('should return positive value of given negative number', function() {
            expect(Math.abs(-5)).to.be.equal(5);
        });

        it('should return positive value of given positive number', function() {
            expect(Math.abs(3)).to.be.equal(3);
        });

        it('should return 0 given 0', function() {
            expect(Math.abs(0)).to.be.equal(0);
        });
    });
});

Running the tests would then give you the output:

$ mocha .


  Math
    #abs()
      ✓ should return positive value of given negative number 
      ✓ should return positive value of given positive number 
      ✓ should return 0 given 0 


  3 passing (11ms)

Expanding even further (I promise, this is the last one I'll show), you might even have tests for multiple methods in a single file. In this case the methods are grouped by the Math object:

"use strict";

var expect = require('chai').expect;

describe('Math', function() {  
    describe('#abs()', function() {
        it('should return positive value of given negative number', function() {
            expect(Math.abs(-5)).to.be.equal(5);
        });

        it('should return positive value of given positive number', function() {
            expect(Math.abs(3)).to.be.equal(3);
        });

        it('should return 0 given 0', function() {
            expect(Math.abs(0)).to.be.equal(0);
        });
    });

    describe('#sqrt()', function() {
        it('should return the square root of a given positive number', function() {
            expect(Math.sqrt(25)).to.be.equal(5);
        });

        it('should return NaN for a given negative number', function() {
            expect(Math.sqrt(-9)).to.be.NaN;
        });

        it('should return 0 given 0', function() {
            expect(Math.sqrt(0)).to.be.equal(0);
        });
    });
});

Output:

$ mocha .


  Math
    #abs()
      ✓ should return positive value of given negative number 
      ✓ should return positive value of given positive number 
      ✓ should return 0 given 0 
    #sqrt()
      ✓ should return the square root of a given positive number 
      ✓ should return NaN for a given negative number 
      ✓ should return 0 given 0 


  6 passing (10ms)

Okay, you get the idea.

Admittedly, most unit tests aren't this simple. A lot of times you'll probably need other resources to perform your tests, like a database, or some other external resource. In order to set this up, we can use one or more of the following Mocha hook methods:

  • before(): Runs before all tests in the given block
  • beforeEach(): Runs before each test in the given block
  • after(): Runs after all tests in the given block
  • afterEach(): Runs after each test in the given block

These hooks are the perfect place for performing setup and teardown work required for your tests. As I already mentioned, one of the common use-cases is to establish a connection to your database before running the tests, which is shown in the following example:

"use strict";

var expect = require('chai').expect;  
var Camo = require('camo');  
var User = require('../models').User;

describe('Users', function() {

    var database = null;

    before(function(done) {
        Camo.connect('mongodb://localhost/app_test').then(function(db) {
            database = db;
            return database.dropDatabase();
        }).then(function() {}).then(done, done);
    });

    afterEach(function(done) {
        database.dropDatabase().then(function() {}).then(done, done);
    });

    describe('#save()', function() {
        it('should save User data to database', function(done) {
            // Use your database here...
        });
    });

    describe('#load()', function() {
        it('should load User data from database', function(done) {
            // Use your database here...
        });
    });
});

Before any of the tests are run, the function sent to our before() method is run (and only run once throughout the tests), which establishes a connection to the database. Once this is done, our test suites are then run.

Since we wouldn't want the data from one test suite to affect our other tests, we need to clear the data from our database after each suite is run. This is what afterEach() is for. We use this hook to clear all of the database data after each test case is run, so we can start from a clean slate for the next tests.

Running Tests

For the majority of cases, this part is pretty simple. Assuming you've already installed Mocha and navigated to the project directory, most projects just need to use the mocha command with no arguments to run their tests.

$ mocha


  Math
    #abs()
      ✓ should return positive value of given negative number 
      ✓ should return positive value of given positive number 
      ✓ should return 0 given 0 
    #sqrt()
      ✓ should return the square root of a given positive number 
      ✓ should return NaN for a given negative number 
      ✓ should return 0 given 0 


  6 passing (10ms)

This is slightly different than our previous examples since we didn't need to tell Mocha where our tests were located. In this example, the test code is in the expected location of /test.

There are, however, some helpful options you may want to use when running tests. If some of your tests are failing, for example, you probably don't want to run the entire suite every time you make a change. For some projects, the full test suite could take a few minutes to complete. That's a lot of wasted time if you really only need to run one test.

For cases like this, you should tell Mocha which tests to run. This can be done using the -g <pattern> or -f <sub-string> options.

Again, using the examples from above, we can use the -g option to only run our #sqrt() tests:

$ mocha -g sqrt


  Math
    #sqrt()
      ✓ should return the square root of a given positive number 
      ✓ should return NaN for a given negative number 
      ✓ should return 0 given 0 


  3 passing (10ms)

Notice that the #abs() tests were not included in this run. If you plan accordingly with your test names, this option can be utilized to only run specific sections of your tests.

These aren't the only useful options, however. Here are a few more options for Mocha that you might want to check out:

  • --invert: Inverts -g and -f matches
  • --recursive: Include sub-directories
  • --harmony: Enable all harmony features in Node

You can check out the full list of options by using the mocha -h command, or on this page.

Conclusion

Keep in mind that both Mocha and Chai can be used for testing just about any type of Node project, whether it's a library, command-line tool, or even a website. Utilizing the various options and plugins available to you, you should be able to satisfy your testing needs pretty easily. Each of these libraries are very useful for validating your code and should be used in just about all of your Node projects.

Hopefully this has served as a useful introduction to Mocha and Chai. There is a lot more to learn than what I've presented here, so be sure to check out the docs for more info.

Have any helpful tips for writing Mocha/Chai tests? Let us know in the comments!