Home

Awesome

Learn Mocha Build Status Test Coverage Code Climate devDependency Status

Quick Guide to mocha.js Test Driven Development (TDD) in node.js

Note: This tutorial is an intro to Testing with Mocha. If you are looking for a more detailed Test Driven Development (TDD) Tutorial see: https://github.com/nelsonic/learn-tdd

Cowboy Coder

We all know Cowboy Coders. (If you don't, it's you!)

The "I just get things done" developer who writes "quick fixes" and maintains "I don't have time to write tests" or "Writing tests for my code takes longer" and then acts surprised when everything starts breaking ... "it was working this morning" ...


Installation

npm install mocha -g --save-dev

You should see some output confirming it installed:

Mocha Installed

More info: http://mochajs.org/#installation

Tip: avoid installing node.js modules using sudo
see: http://stackoverflow.com/questions/16151018/npm-throws-error-without-sudo

First Tests

Create Test Directory

In your project create a new /test directory to hold your tests:

mkdir test

Create test.js File

Now create a new file ./test/test.js in your text editor

and write/paste the following code:

var assert = require("assert"); // node.js core module

describe('Array', function(){
  describe('#indexOf()', function(){
    it('should return -1 when the value is not present', function(){
      assert.equal(-1, [1,2,3].indexOf(4)); // 4 is not present in this array so indexOf returns -1
    })
  })
});

Run Test

By typing the command mocha in your terminal the mocha comand line program will look for a /test directory and run any .js files it contains:

mocha

Mocha 1 Test Passes

A More Useful TDD Example (Cash Register Mini Project)

While I'm the first to agree that cash-less payments are the future, paying with cash is something everyone can relate to and is therefore a good example to use. (think of better TDD example? tell me!)

Basic Requirements

Given a Total Payable and Cash From Customer Return: Change To Customer (notes and coins).

Essentially we are building a simple calculator that only does subtraction (Cash - Total = Change), but also splits the result into the various notes & coins.

In the UK we have the following Notes & Coins:

GBP Notes GBP Coins

see: http://en.wikipedia.org/wiki/Banknotes_of_the_pound_sterling (technically there are also £100 and even £100,000,000 notes, but these aren't common so we can leave them out. ;-)

If we use the penny as the unit (i.e. 100 pennies in a pound) the notes and coins can be represented as:

this can be represented as an Array:

var coins = [5000, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 2, 1]

Note: the same can be done for any other cash system ($ ¥ €) simply use the cent, sen or rin as the unit and scale up notes.

The First Test

If you are totally new to TDD I recommend reading this intro article by Scott Ambler (especially the diagrams) otherwise this (test-fail-code-pass) process may seem strange ...

In Test First Development (TFD) we write a test first and then write the code that makes the test pass.

so, back in our ./test/test.js file add the following line:

var C = require('../cash.js');  // our module

Watch it Fail

Back in your terminal window, re-run the mocha command and watch it fail:

mocha

Mocha TFD Fail

This error ("Cannot find module '../cash.js'") is pretty self explanatory. We haven't created the file yet so test.js is requesting a non-existent file!

Q: Why deliberately write a test we know is going to fail...? <br /> A: To get used to the idea of only writing the code required to pass the current (failing) test.

Create the Module File

Create a new file for our cash register cash.js:

touch cash.js

Note: We are not going to add any code to it just yet.

Re-run the mocha command in terminal, it will pass (zero tests)

Mocha Pass 0 Tests

Lets add a test to ./test/test.js and watch it fail again:

var assert = require("assert"); // core module
var C = require('../cash.js');  // our module

describe('Cash Register', function(){
  describe('Module C', function(){
    it('should have a getChange Method', function(){
      assert.equal(typeof C, 'object');
      assert.equal(typeof C.getChange, 'function');
    })
  })
});  

Re-run mocha:

Mocha 1 Test Failing

Write Just Enough Code to Make the Test Pass

Add the following to cash.js:

var C = {};                    // C Object simplifies exporting the module
C.getChange = function () {    // enough to satisfy the test
    'use strict';
    return true;               // also passes JSLint
};
module.exports = C;            // export the module with a single method

Re-run mocha (now it passes):

Mocha 1 Test Passes

Write A Real Test

Going back to the requirements, we need our getChange method to accept two arguments/parameters (totalPayable and cashPaid) and return an array containing the coins equal to the difference:

e.g:

totalPayable = 210         // £2.10
cashPaid     = 300         // £3.00
difference   =  90         // 90p
change       = [50,20,20]  // 50p, 20p, 20p

Add the following test to ./test/test.js:

it('getChange(210,300) should equal [50,20,20]', function(){
    assert.deepEqual(C.getChange(210,300), [50,20,20]);
})

Note: use assert.deepEqual for arrays see: http://stackoverflow.com/questions/13225274/

Mocha Assertion Error

Write the Method to Pass the Test

What if I cheat?

C.getChange = function (totalPayable, cashPaid) {
    'use strict';
    return [50, 20, 20];    // just enough to pass :-)
};

This will pass:

Mocha Passing

This only works once. When the Spec (Test) Writer writes the next test, the method will need to be re-written to satisfy it.

Let's try it. Work out what you expect:

totalPayable = 486           // £4.86
cashPaid     = 1000          // £10.00
difference   = 514           // £5.14
change       = [500,10,2,2]  // £5, 10p, 2p, 2p

Add the following test to ./test/test.js and re-run mocha:

it('getChange(486,1000) should equal [500, 10, 2, 2]', function(){
    assert.deepEqual(C.getChange(486,1000), [500, 10, 2, 2]);
})

As expected, our lazy method fails:

Mocha 3 Test Fails

Keep Cheating or Solve the Problem?

We could keep cheating by writing a series of if statements:

C.getChange = function (totalPayable, cashPaid) {
    'use strict';
    if(totalPayable == 486 && cashPaid == 1000)
        return [500, 10, 2, 2];
    else if(totalPayable == 210 && cashPaid == 300)
        return [50, 20, 20];
};

The Arthur Andersen Approach gets results:

Mocha 3 Passing

But it's arguably more work than simply solving the problem. Let's do that instead. (Note: this is the readable version of the solution! feel free to suggest a more compact algorithm)

var C = {};     // C Object simplifies exporting the module
C.coins = [5000, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 2, 1]
C.getChange = function (totalPayable, cashPaid) {
    'use strict';
    var change = [];
    var length = C.coins.length;
    var remaining = cashPaid - totalPayable;          // we reduce this below

    for (var i = 0; i < length; i++) { // loop through array of notes & coins:
        var coin = C.coins[i];

        if(remaining/coin >= 1) { // check coin fits into the remaining amount
            var times = Math.floor(remaining/coin);        // no partial coins

            for(var j = 0; j < times; j++) {     // add coin to change x times
                change.push(coin);
                remaining = remaining - coin;  // subtract coin from remaining
            }
        }
    }
    return change;
};

Add one more test to ensure we are fully exercising our method:

totalPayable = 1487                                 // £14.87  (fourteen pounds and eighty-seven pence)
cashPaid     = 10000                                // £100.00 (one hundred pounds)
difference   = 8513                                 // £85.13
change       = [5000, 2000, 1000, 500, 10, 2, 1 ]   // £50, £20, £10, £5, 10p, 2p, 1p
it('getChange(1487,10000) should equal [5000, 2000, 1000, 500, 10, 2, 1 ]', function(){
    assert.deepEqual(C.getChange(1487,10000), [5000, 2000, 1000, 500, 10, 2, 1 ]);
});

Mocha 4 Passing


Bonus Level

Code Coverage

We are using istanbul for code coverage. If you are new to istanbul check out my brief tutorial: https://github.com/nelsonic/learn-istanbul

Install istanbul:

npm install istanbul -g

Run the following command to get a coverage report:

istanbul cover _mocha -- -R spec

You should see:

Istanbul Coverage

or if you prefer the lcov-report:

Istanbul Coverage Report

100% Coverage for Statements, Branches, Functions and Lines.

Travis

If you are new to Travis CI check out my tutorial: https://github.com/nelsonic/learn-travis

Visit: https://travis-ci.org/profile Enable Travis for learn-travis project

Travis Enabled

Done. Travis Build Status


Background

What is Mocha?

Mocha is a JavaScript test framework running on node.js and the browser.

Mocha Logo

Made by TJ Holowaychuk creator of Express (by far the most popular node.js web framework), Mocha is TJ's answer to the problem of testing JavaScript.

Why Mocha?

At last count there were 83 testing frameworks listed on the node.js modules page: https://github.com/joyent/node/wiki/modules#wiki-testing this is both a problem (too much choice can be overwhelming) and good thing (diversity means new ideas and innovative solutions can flourish).

There's no hard+fast rule for "which testing framework is the best one?"

Over the past 3 years I've tried: Assert (Core Module), Cucumber, Expresso, Jasmine, Mocha, Nodeunit, Should, and Vows

My criteria for chosing a testing framework:

Advanced:

Notes

Other Mocha Tutorials/Background

Test Driven Development (TDD) Background/Philosophy

Further Reading


Trying to think of a good example for TDD ...

Rant

Code without tests is like a building without a foundation!

Building Collapse

It's only a matter of time before it all comes crashing down ...

Is Test Driven Development (TDD) a silver bullet for all my software development woes? Short answer: No. There is a lot more that goes into writing great software than just having tests. But without tests reliability is impossible.

If you are not doing TDD in your projects I'm probably not going to be the one to change your mind by evangelizing about it. I know plenty of people calling themesleves "developers" who stubbornly cling to the idea that testing is for "QA" or "That's why we have testers" and wish them nothing but the best of luck! I just can't work with you or use your "product", no hard feelings. :-)