Test Driven Development in JavaScript with Mocha and Chai

What is this TDD We Keep Hearing About?

A test-first software development process. TDD is characterized by a Red-Green-Refactor workflow.

  • Red: a developer writes an initially failing automated test case that defines a new function or a new feature.
  • Green: The developer produces the minimum amount of code to make the new test pass.
  • Refactor: clean-up the code to acceptable coding standards.

One of the important concepts of TDD is to automate the test cycle, and to be able to run all of the tests in the
test suite in an automated fashion. Green is achieved when not only the new test passes, but all tests in the test
suite pass.

One of the advantages of the approach is that the TDD cycle tends to break down larger tasks into small, manageable
discreet tasks, and small code blocks that can be tested independently. Quality of code should increase with better
test coverage.

There are several drawbacks. For one, developers tend to write more code, and the maintenance of the tests adds to the
overhead of the project. Also, it is difficult to address some complex functional and integration testing scenarios;
developers tend to use mocks for database and network integration, and TDD can hide potential problems until later
in the cycle.

What is Mocha?

According to the Web site …

Mocha is a feature-rich JavaScript test framework running on node and the browser, making asynchronous testing simple
and fun. Mocha tests run serially, allowing for flexible and accurate reporting, while mapping uncaught exceptions
to the correct test cases.

Mocha provided several of the project goals for a testing framework:

  • Strong asynchronous testing
  • Automation
  • Strong community support
  • Examples and documentation

In addition, Mocha provides a flexible UI, flexible reporting options and support for running unit tests in a Web browser.

What is Chai?

According to the Web site …

Chai is a BDD / TDD assertion library for node and the browser that can be delightfully paired with any javascript testing framework.

Getting Started

To run the example code, you will need to have Node.js and the Node Package Manager (NPM) installed. Please see
The Node Website for details on how to install Node.js.

Once you have Node.js installed, to run the example unit tests:

  1. Clone the project from the Digital River gitHub server
  2. Change into the project directory
  3. Install the project dependencies with NPM
$ git clone http://github.digitalriverws.net/cpryce/tddWithMocha.git
$ cd tddWithMocha
$ npm install

Depending on your system, you may need to run the install command as root, or use sudo to execute the command with root privileges.

The install command will install Mocha and Chai Node.js modules and their dependencies.

Mocha Tests

All of the Mocha framework tests are in the /test directory. mocha-test.js is an example that illustrates the
testing framework and the default assertion library that is installed with Node.js.

The npm test command invokes the Mocha test runner. It is equivalent to typing:

$ mocha -w --reporter spec

However, with the npm test command, mocha does not need to be installed globally.

Tutorial 1: Simple Testing Using Chai

To illustrate the use of the chai module and Test-Driven Development (TDD) in JavaScript, we are going to create a short JavaScript module. The module will validate a string input, which will only contain left parentheses ‘(‘ and right parentheses ‘)’ characters. The module will return true if the input string contains a valid combination of parentheses; i.e., there are as many closing parentheses as there opening parentheses.

'()'   -> valid
'()()(()); -> valid
'()('  -> invalid (not closed properly)
')()(' -> invalid (not nested properly)

The Tutorial

You can complete this tutorial by following the steps outlined. I recommend that you checkout a new branch to complete the work. You can do this easily after have cloned the repository with the following command:

$ git checkout -b myTutorial1

If you want to see the completed tutorial, it is available in the tutorial-1 branch. Checkout the completed tutorial and unit tests with the following command:

$ git checkout tutorial-1

To begin, create the skeleton module. Add a folder to the file system called validParentheses. Insert a file in the directory must created. Name the file index.js.

/validParentheses/index.js

module.exports = function validParentheses(parens) {
    return null;
};

Now, we will write a failing unit test(s) and then we will code in index.js until our code passes. Create a new file in
the test directory called validParentheses-test.spec.js.

/test/validParentheses-test.spec.js

// import the expect BDD module
var expect = require('chai').expect;

// import the code that we are testing.
var validParentheses = require('../validParentheses');

// create a grouping for our tests with the describe function
describe("Valid Parentheses", function () {
    // run a unit test
    it(": should return true for valid values", function () {
        expect(validParentheses('()')).to.be.true;
    });
});

Line 2 imports the chain.js module, specifying the expect package.

Line 4 brings our JavaScript module into scope in the
test file.

In line 6, we create a scope to group unit tests together. If we had other functions in the ValidParentheses package to
test we might organize groups of related functions this way.

Line 10 executes the unit test, running the validParentheses() function inside a scoped block. The expect() function
will pass if the call to validParentheses returns true.

Start the Mocha test runner

To execute the test, give the following command in the terminal window.

$ mocha -w --reporter spec

The test runner will look for any file in /test that ends with a .js and execute it as a unit test file. In this case,
there is only a single file. The output should look something like this:

$ mocha -w --reporter spec
<ins datetime="2014-11-28T05:16:39+00:00"></ins>

Valid Parentheses
    1) : should return true for valid values

0 passing (5ms)
  1 failing

1) Valid Parentheses : should return true for valid values:
     AssertionError: expected undefined to be true

This is expected, our validParentheses module doesn’t return a value yet. Let’s fix it so that the test pass. Insert
the following lines of code at line 2:

/validParentheses/index.js

    return true;

As soon as you save the file, the test framework should re-run the tests, and now you should see a passing test.

Great! We coded just enough to pass our test. So we need to create a new failing test that further helps us to develop our module. We will insert a new failing test. Insert the following lines of code into
test/validParentheses-test.spec.js after line 13:

/test/validParentheses-test.spec.js

    it(": should return false for invalid values", function () {
        expect(validParentheses('()(')).to.not.be.true;
    });

As soon as you save the file, the test runner should pick up the changes and run all of the tests again. Now, there should be 1 passing test and one failing test. The problem is that we are not returning false for an invalid value. So we need to edit our code to pass both tests.

Our first algorithm is very simple. If the number of opening parens is not equal to the number of closing parens, the string is invalid. Replace the contents of validParentheses/index.js with this code:

/validParentheses/index.js

    var count = 0;
    for (var c in parens) {
        if (parens[c] === '(') {
            count += 1;
        }
        if (parens[c] === ')') {
            count -= 1;
        }
    }
    return count === 0;

You should immediately see two green tests! Green tests are good.

Expanding the Tests

So far, we have a pretty simple module, but if we examine the test cases, we can see that we are not testing for all of the possible cases. Let’s insert two new test cases into our test spec file, directly after the first test for invalid values.

/test/validParentheses-test.spec.js

    expect(validParentheses('())(')).to.not.be.true;
    expect(validParentheses(')(()')).to.not.be.true;

Now with our test failing, we can clean up the code. Building on our existing. In the first case, count would have gone
to -1 when the invalid closing tag was encountered. In the second, we can invalidate the entire string because the
initial tag is a closing tag. Let’s fix our validator:

/validParentheses/index.js

    if (parens.charAt(0) === ')') {
        return false;
    }
    var count = 0;
    for (var c in parens) {
        if (parens[c] === '(') {
            count += 1;
        }
        if (parens[c] === ')') {
            count -= 1;
            if (count < 0) {
                return false;
            }
        }
    }
    return count === 0;

Now all of our tests should pass. To make sure, let’s add a more complex example in the passing tests:

/test/validParentheses-test.spec.js

    expect(validParentheses('(())()((()))')).to.be.true;

Red, Green, Refactor

Now that we are fairly confident that our code has test coverage, we can see if there are any improvements that we want
to make to the code. For instance, the code style is not very compact, we could improve on that with confidence. Our
tests will let us know if we have broken anything.

/validParentheses/index.js (final listing)

module.exports = function validParentheses(parens) {
    if (parens.charAt(0) === ')') {
        return false;
    }
    var count = 0;
    for (var c in parens) {
        if (parens[c] === '(') {
            count++;
        }
        if (parens[c] === ')' && --count < 0) {
            return false;
        }
    }
    return count === 0;
};

/test/validParentheses-test.spec.js (final listing)

// import the expect BDD module
var expect = require('chai').expect;

// import the code that we are testing.
var validParentheses = require('../validParentheses');

// create a grouping for our tests with the describe function
describe("Valid Parentheses", function () {
    // run a unit test
    it(": should return true for valid values", function () {
        expect(validParentheses('()')).to.be.true;
        expect(validParentheses('(())()((()))')).to.be.true;
    });
    it(": should return false for invalid values", function () {
        expect(validParentheses('()(')).to.not.be.true;
        expect(validParentheses('())(')).to.not.be.true;
        expect(validParentheses(')(()')).to.not.be.true;
    });
});

Leave a Reply

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