in Code

Test Driven Development with Mocha and Node


Testing is tantamount to any development process, be it through a dedicated person clicking around and trying to break what functions they know about and how to break, or a smoke test to get an idea about what works – but what about those scenarios you don’t immediately think about? How do you catch the things you don’t know about? That’s where Test Driven Development comes in, and it’s a lot easier to implement than it may sound thanks to robust testing frameworks. In this instance we’re going to walk through creating a simple ES6 class in JS/Node and then test against it using Mocha.

Why Test Driven Development (TDD)?


Typically we would start out by creating our class and then make it work like we want to, and then implement it and after that maybe test it out by banging on it for a while. That’s fine, but it’s not entirely effective. Going in without a plan, or even with a plan if you know what you want to do, can still leave stones unturned. With TDD we’re going to write the test first and then write our class that we want to test. The idea behind this is to create all the points that we need to hit, fail miserably the first time around, then then jump into creating the class, function, etc and meeting all the tests.

  • Write test → fail tests
  • Write function → pass tests
  • Refactor function → restart test process

TDD is a cyclical process of failure and success, but is a much more solid method of keeping your code, your ideas, and your process in check by forcing you to focus on what matters functionally and keeping your code from falling to regressions as you move forward – especially in large, interconnected applications.

So what are the benefits of TDD? As stated above keeping your code and ideas focused is great as is avoiding regression, but those are things that everyone should want to achieve and could be achieved just as easily without adopting a TDD workflow. While there are a few interpretations into the main benefits of TDD, here are my beliefs as to what the primary benefits are:

  • Sanity Check – By iterating on tests you’ll spend less time swearing “it should work!” and more time knowing how it does work and have support to that claim.
  • Modularity – Unit testing is all about testing individual parts of your code, typically individual functions. By doing so we are subconsciously forcing ourselves to write smaller, more modular functions that serve specific purposes. As you’ll see further on: testing is “function a() returns an object with 2 keys if param1 is a string and then if function b() returns an …” is infinitely more complicated than being able to pinpoint “function getId() returns an integer otherwise returns an error”.
  • Progressive Improvements – As stated earlier, TDD is cyclical. You test, you fail, you fix, you refactor, you start again. By progressively refactoring your code you are consistently improving it rather than sticking to “what works, works” mentality.
  • Self-Writing Documentation – I love clear, concise documentation; but once you start down the path of complex application structures you don’t always know what does what and where. Clean code can only go so far. By having an executable unit test you’re giving yourself and future developers the opportunity to see what exists in your application, what they do, and whether they work or not. It saves on documentation maintenance and onboarding time.

TDD in Practice


So what are going to test that’s easy to understand and fun to do? Well, beers; we should definitely order some beers. Well, not really, we’re going to create an Order class to order a number of beers and have that class return that number of beers to us. Pretty simple – put number in, get number back, how can a user mess that up? Like this:

Let’s get started by installing our modules. Create a new folder, point your terminal there, and let’s install our testing framework (Mocha) and any additional modules we may need (Chai, expect.js), and create our test directory:

> npm install mocha chai expect.js
> mkdir test
> touch test/order.js

At this point we have Mocha installed, our modules installed, and we have a test directory with an empty file in it. Mocha by default will use any .js or .coffee files in the ./test directory in alphabetical order. So you can break up your tests into multiple files and keep everything nice and tidy. In our case, since we’re going to create an Order class we’ll have our ./test/order.js file to house all of our Order tests. For now, let’s run Mocha in our test directory and see what happens:

> mocha

mocha_dry

Hey! Everything is passing, so that’s something! Now we get to write some of our tests (the fun stuff!). Open up the ./test/order.js file in your editor of choice and let’s get to writing out our tests.

To start with, we need to include our testing modules; so let’s include them at the top of our test file:

const assert = require('chai').assert;
const expect = require('expect.js);

Next we’ll include the base test suite for our Order:

describe('Order', function(){
    describe('.beers', function(){

    /** Put tests in here */

    });
});

What we have above is a definition for Mocha to run a test for Order and inside that test we have a subtest for .beers. Using the describe() function you can name your tests whatever you want. With this format, personally, I prefer to name out my tests semantically based on how the class, object, etc are being used programmatically which, in this case, we’ll plan on using it as Order.beers. With that in place, let’s rerun Mocha and see what happens.

mocha_dry

A lot more of nothing. But at least nothing has blow up, so that’s good. Let’s create a sample assertion inside our .beers test and see that in action.

describe('Order', function(){
    describe('.beers', function(){

        /** Test if the value is a string */
        it('should be a string', function(){
            var testValue = 'test';
            expect(testValue).to.be.a('string');
        });

    });
});

Above we’re giving Mocha an assertion to test against using the it() function inside of our describe(‘.beers’) test. What it() is doing here is taking a string as the first parameter to identify the test; this is for our reference so in this instance we’re identifying it as should be a string because we’re going to be testing for it. Rule of thumb with Mocha – name your tests based on what the function you are testing should do with the assertion you’re about it give it. Doing that will demystify your tests and make them more readable for anyone who comes along later. Inside that assertion we are setting a testValue as a string ‘test’ and then using expect.js to assert what we expect to be true in as plain english as the module allows: expect our test value to be a string.

So what happens when we run Mocha now?

 

mocha_success

A passing test, we’re well on our way! Above we can see that the test is running, in order, Order → .beers → should be a string. Let’s make that fail by changing testValue to a number and check out a failing test:

 

mocha_fail

Oh no, a failure! With the failure above, each failing test will be given a number and then listed at the end of the terminal return with the exact failure. In this case, we have 1 failure, and the failure condition for 1 is expected 123 to be a string. That’s pretty easy to understand, thanks Mocha! So how about we take build out our test cases for our Order class?

For brevity’s sake, here is what we will be asserting: “does Order.beers return a positive number? Does Order.beers return an error if we supply new Order() with anything other than a positive number?”. Remember, we’re going to be ordering -1 beers and a lizard at some point so we need to be sure that’s taken into account. Here’s the entirety of the test we’ll be running:

/** Include npm modules */
const assert = require('chai').assert;
const expect = require('expect.js');

/** Include Order class */
const Order = require('../modules/order.js');

/** Create test Orders */
var positiveOrder = new Order(123);
var negativeOrder = new Order(-123);
var partialOrder = new Order(1.23);
var lizardOrder = new Order('lizard');

describe('Order', function(){
    describe('.getBeers', function(){

        /** number */
        it('should be a number', function(){
            expect( positiveOrder.getBeers() ).to.be.a('number');
        });

        /** whole number */
        it('return an error if not a whole number', function(){
            assert.throws( function(){partialOrder.getBeers()} , "not a whole number");
        });

        /** positive number */
        it('should be a positive number', function(){
            expect( positiveOrder.getBeers() ).to.be.above(0);
        });

        /** negative number */
        it('should return an error if negative number', function(){
            assert.throws( function(){negativeOrder.getBeers()} , "not greater than 0");
        });

        /** lizard order */
        it('should return an error if you order a lizard', function(){
            assert.throws( function(){lizardOrder.getBeers()} , 'not a number' );
        });

    });
});

And at last create a new file in directory ./modules called order.js and fill it up with:

'use strict';

module.exports =
class Order {
    constructor(numberOfBeers){
        this.numberOfBeers = numberOfBeers;
    }

    get beers(){
        return this.getBeers();
    }

    getBeers(){
        if(typeof this.numberOfBeers == 'number'){
            if(this.numberOfBeers > 0){
                if(this.numberOfBeers % 1 === 0){
                    return this.numberOfBeers;
                }else{
                    throw new Error("not a whole number");
                }
            }else{
                throw new Error("not greater than 0");
            }
        }else{
            throw new Error("not a number");
        }
    }
}

And with those in place, let’s run that test and see what happens with our new test and the Order class in existence:

mocha_final Test Driven Development

Perfect! We are testing our new Order object to be sure that it does the following:

  • should be a number
  • return an error if not a whole number (no fractions)
  • should be a positive number (greater than 0)
  • should return an error if negative number
  • should return an error if you order a lizard

The sample we made above was longer than just creating a function to return what we will need, but we know for certain what our function does, what it doesn’t do, and where it errors or succeeds. As we build out more and more around or onto the Order class we can rerun these tests and write additional tests to check for regressions and improve on what we wrote to satisfy the initial tests.

In Conclusion


Adopting a workflow like Test Driven Development isn’t all about code and frameworks, it’s a mindset that you have to commit to. Pulling yourself out of the moment to determine what to achieve is what TDD is at it’s heart. I prefer to write down what I intend to achieve and then write the tests based on that to stay organized, but it’s still a mindset of knowing exactly what I’m going to do before putting down code – the equivalent of a brainstorm before writing a script. Sure, you could start writing a script without a clear goal, but you’re going to spend a lot of time retconning and losing time in the long run. By having all of your goals upfront and the means to satisfy those goals is what will give you an edge to deliver a better product to happier clients.