Testing Express with Mocha and Chai

I don’t want to write another testing tutorial. It will be the only kind-of tutorial because I simply want to write down what I did to introduce tests to my first Node application. I chose Mocha as the test framework and Chai as an assertion library because they are well-documented and there is already a lot of information about both.

Starting with Mocha alone

I installed Mocha globally because I think that it will be used in various projects. You can install with npm like this:

npm install -g mocha

If you think that you will need it only for this particular project, you can install locally like so:

npm install --save-dev mocha

You are now ready to add test framework to your package.json file:

//...
"scripts": {
  "start": "node ./bin/www",
  "test": "mocha"
}
//...

I used the sample code to create the dummy test in /test/index.js (the code was taken from the great post on the Alexander Paterson blog)

describe('Our application', function() {

  // This is the name of the test
  it('should understand basic mathematical principles', function(done) {

    // We want this test to pass.
    if (5 == 5) {
      // If the behavior is as expected, call done with no argument.
      done();
    } else {
      // Otherwise, call done with an error.
      done(new Error("Not sure what's happened."));
    }

  });

}); 

The above test is working and is testing only if the tests are working. No application itself is executed and you should get your first green checkmark just by typing “mocha” in the console – this command executed in the project directory will load tests from the /test/ directory and execute them one by one.

Testing middleware

At this moment I don’t want to test request-response path and routing. I want to test particular middleware functions to make sure that they are working as expected. I already moved my middleware functions to the external module and they are called from the application in particular routes.

Because they are Express middleware functions, they expect request and response objects to be passed as the execution parameters. There is a library which is able to generate proper mocks of these objects: node-mocks-http. As you may expect, the installation is rather simple:

npm install --save-dev mode-mocks-http

I created the separate test file to test the basic behavior of my tools – the first test is checking whether my menu generator generates any menu items. The second one checks if the proper menu item is checked. I placed the code in /test/tools.js file:

var httpMocks = require('node-mocks-http');
var tools = require('../modules/tools');

describe('/modules/tools.js ', function() {

    // This is the name of the test
    it('generateMainMenu should generate non-empty res.locals.menuItems array', function(done) {
        var req = httpMocks.createRequest({
            _parsedUrl: {
                pathname: "/siteAdmin"
            }
        });
        var res = httpMocks.createResponse();
        var next = function() {};

        // call the actual function
        tools.generateMainMenu(req, res, next);

        if ((res.locals.menuItems instanceof Array) && (res.locals.menuItems.length > 0)) {
            // If the behavior is as expected, call done with no argument.
            done();
        } else {
            // Otherwise, call done with an error.
            done(new Error("menuItems not created properly"));
        }

    });

    // This is the name of the test
    it('generateMainMenu should highlight proper menu item', function(done) {
        var req = httpMocks.createRequest({
            _parsedUrl: {
                pathname: "/siteAdmin"
            }
        });
        var res = httpMocks.createResponse();
        var next = function() {};

        // call the actual function
        tools.generateMainMenu(req, res, next);

        if (res.locals.menuItems[2].class == 'menu-selected') {
            // If the behavior is as expected, call done with no argument.
            done();
        } else {
            // Otherwise, call done with an error.
            done(new Error("Proper menu item not selected"));
        }

    });

});

I expected green checkmarks and I got them. Later I changed few things to break the code and to make sure that my tests are working fine. I’m aware that  I should write my tests first, but I’m not an experienced TDD programmer so I treat this as an additional exercise and learning experience.

Let’s add some Chai

As the last step in my testing exercise, I added Chai assertion library to my stack. As you may expect, installation is again rather easy:

npm install --save-dev chai

What is Chai for? It gives you set of assertions you can use in your test code to make them more readable and clean. For example, in my test above, I have:

//...
        if ((res.locals.menuItems instanceof Array) && (res.locals.menuItems.length > 0)) {
            // If the behavior is as expected, call done with no argument.
            done();
        } else {
            // Otherwise, call done with an error.
            done(new Error("menuItems not created properly"));
        }
//...

Which is now replaced with the Chai assertion:

//...
        expect(res.locals.menuItems).to.be.a('Array').with.lengthOf.above(1);
//...

Beautiful, isn’t it? You should note that done() is not called now, this means that you should also remove it from the declaration of the function:

//...
// This code:
    it('generateMainMenu should generate non-empty res.locals.menuItems array', function(done) {
// is now replaced with:
    it('generateMainMenu should generate non-empty res.locals.menuItems array', function() {
//...
// And this code:
    it('generateMainMenu should highlight proper menu item', function(done) {
// is now replaced with:
    it('generateMainMenu should highlight proper menu item', function() {
//...

Both tests are now implemented like this:

var httpMocks = require('node-mocks-http');
var expect = require('chai').expect;
var tools = require('../modules/tools');

describe('/modules/tools.js ', function() {

    describe('generateMainMenu', function() {
        it('should generate non-empty res.locals.menuItems array', function() {
            var req = httpMocks.createRequest({
                _parsedUrl: {
                    pathname: "/siteAdmin"
                }
            });
            var res = httpMocks.createResponse();
            var next = function() {};

            tools.generateMainMenu(req, res, next);
            expect(res.locals.menuItems).to.be.a('Array').with.lengthOf.above(1);
        });

        it('should highlight proper menu item', function() {
            var req = httpMocks.createRequest({
                _parsedUrl: {
                    pathname: "/siteAdmin"
                }
            });
            var res = httpMocks.createResponse();
            var next = function() {};

            tools.generateMainMenu(req, res, next);
            expect(res.locals.menuItems[2].class).to.equal('menu-selected');
        });
    })

});

The test is now much shorter and it is also much more readable.

Conclusion

Testing Express application middleware is not that hard. I was able to write much more tests later and I have most of my code covered with tests right now. Next step will be to create tests based on the routes. This way I will be able to check if particular pages are displaying proper values and if they are working fine. Currently, I have not much to test in this area, so this will have to wait.

You can find this project source on GitHub.

Leave a Reply

Your email address will not be published. Required fields are marked *