Sunday, February 2, 2014

Setting Up Continuous Testing with Grunt and Mocha

"To grunt and sweat under a weary life"
-- Shakespeare, Hamlet
Act III, Scene 1, Line 77

Prelude


My first experience of continuous testing was using NCrunch, it was funny how quickly this change the way I wrote software.

What is Continuous Testing


In a typical TDD style of programming you do the following.



  • Write a failing test.
  • Stop and run test to prove that test is failing.
  • Write code to pass failing test.
  • Stop and run test to prove that test is now passing (and make sure nothing is broken).
  • Refactor code.
  • Stop and run test to prove nothing as been broken by refactoring.
  • Repeat.

Notice anything interesting?  Yep, there is a whole lot of stopping in the cycle.  This stopping is one of the common complaints one hears from people just starting out in TDD (I know most of my readers are well past that point, but there are a LOT of programmers that do not do TDD, so this issue is still relevant).

How can we fix this?  It would be nice if we had something running in the background which ran our test for us and would notify us of any failures as they came up, this could work like a word processor's spell check.

I remember when I first started writing on a computer using WordPerfect (yep, I am 33).  Using a word processor back in the dark days when one would start Windows from DOS, one would find themselves following a similar process with a spell checker.  You would do the following.

  • Write some text.
  • Stop and run the spell checker.
  • Fix any issues.
  • Write some more.
  • Stop and run the spell checker again.
  • Fix any issues.
  • Repeat.

This looks a like like the typical TDD session.

Continuous testing is testing in which the test are always running.  It works a lot like a spell checker in a modern word processor.  As you write code your test are continuously running and given you feedback on your code.

With continuous testing, TDD looks like the following.

  • Write a failing test.
  • Write code to pass the test.
  • Refactor.
  • Repeat.

Yep, coding without stopping.  The first time I used NCrunch for a whole day, I noticed when I went to check my code into source control that I had not saved it yet!  The way that NCrunch works it automatically builds and tests your code for you, so you do not even notice that you have not saved yet.  Depending on how much you trust your computer and power supply this may or may not be a good thing, but it shows how a tool can change the way that you work.

The team that I work with have fallen in love with continuous testing so much that when NCrunch went from free to pay we took a quick poll and found that everyone would rather drop their ReSharper licence in order to get a NCrunch licence if push-came-to-shove (ReSharper is a great tool and I would not want to do .Net programming without it, in fact I have a ReSharper T-shirt on as I am typing this (do not judge me), but continuous testing is that important).  Luckily for us, we did not have to follow through with that choice and are now enjoying NCrunch along with ReSharper.

How to Setup Grunt


Enough about NCrunch this post is actually about how to set up a continuous testing environment for Node.js using Grunt and Mocha.  First thing first, you'll need to have Node.js installed if you do not already have it.

Assuming that you have Node.js installed let's look at getting Grunt setup.

The easiest way to install Grunt is via npm (Node Package Manager), fire up the console.

C:\kata>npm install -g grunt-cli


This will install the Grunt Command Line Interface and will add grunt to your system path.

Adding Grunt to Your Projecting

Now that we have the Grunt Command Line Interface install we can add Grunt to our Node.js code.

Let's create a new Grunt Hello World project.

C:\kata>mkdir gruntHelloWorld

C:\kata>cd gruntHelloWorld

C:\kata\gruntHelloWorld>mkdir src

C:\kata\gruntHelloWorld>mkdir test

We now have a directory called gruntHelloWorld which has a src folder for our source code and a test folder for our tests, feel free to code these what you want you'll just have to modify the instructions a bit from here on out.

We are now ready to create a package.json file to hold our Node project dependencies (you can skip this if you want).

npm init will give you a prompt like the following.

C:\kata\gruntHelloWorld>npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sane defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install --save` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
name: (gruntHelloWorld)

Follow the prompt (do not worry you can fix any issues you have later).  Here is what I used incase you want to copy.  Note, for the name you cannot have spaces.  Also note that you can hit enter to just use the default values.

name: (gruntHelloWorld) HelloWorld
version: (0.0.0) 0.0.1
description: A simple example of continuous testing using Grunt and Mocha.
entry point: (index.js) hello.js
test command: grunt
git repository:
keywords: example grunt mocha hello world
author: Mike Harris
license: (BSD-2-Clause)
About to write to C:\kata\gruntHelloWorld\package.json:

{
  "name": "HelloWorld",
  "version": "0.0.1",
  "description": "A simple example of continuous testing using Grunt and Mocha.",
  "main": "hello.js",
  "directories": {
    "test": "test"
  },
  "scripts": {
    "test": "grunt"
  },
  "keywords": [
    "example",
    "grunt",
    "mocha",
    "hello",
    "world"
  ],
  "author": "Mike Harris",
  "license": "BSD-2-Clause"
}


Is this ok? (yes) y

C:\kata\gruntHelloWorld>

What did we just do?  We create a file which will hold all of our dependencies for our Node project, this file is called package.json.

C:\kata\gruntHelloWorld>type package.json
{
  "name": "HelloWorld",
  "version": "0.0.1",
  "description": "A simple example of continuous testing using Grunt and Mocha.",
  "main": "hello.js",
  "directories": {
    "test": "test"
  },
  "scripts": {
    "test": "grunt"
  },
  "keywords": [
    "example",
    "grunt",
    "mocha",
    "hello",
    "world"
  ],
  "author": "Mike Harris",
  "license": "BSD-2-Clause"
}

Let's add Grunt to this project.


C:\kata\gruntHelloWorld>npm install grunt --save-dev


Notice the --save-dev on the npm command, this will add a dependency on grunt to our package.json file, which will allow us to our project dependencies with others.

C:\kata\gruntHelloWorld>type package.json
{
  "name": "HelloWorld",
  "version": "0.0.1",
  "description": "A simple example of continuous testing using Grunt and Mocha.",
  "main": "hello.js",
  "directories": {
    "test": "test"
  },
  "scripts": {
    "test": "grunt"
  },
  "keywords": [
    "example",
    "grunt",
    "mocha",
    "hello",
    "world"
  ],
  "author": "Mike Harris",
  "license": "BSD-2-Clause",
  "devDependencies": {
    "grunt": "~0.4.2"
  }
}

Now that we have Grunt setup, we need to create the Gruntfile.js file.

Adding in the Gruntfile.js


The Gruntfile.js tells Grunt what to do, think of it as an Apache Ant build file for Grunt, but instead of being stored in XML and limited to just building, it is written in JavaScript and is used for tasks (which can include "building").

Create a new file called Gruntfile.js at the root of the project (right next to package.json), I will use the text editor Sublime Text to do this (since it is current favorite text editor, but vim or notepad or ... would work just as well).

Our Gruntfile.js will need to export a function which takes a single argument.

C:\kata\gruntHelloWorld>type Gruntfile.js
module.exports = function(grunt) {
  // add task here
};


Now that we have our wrapper function setup we can add tasks to it.

Adding in Mocha


First let's get the latest version of Mocha from npm.

C:\kata\gruntHelloWorld>npm install mocha --save-dev

Now we will the Grunt plugin grunt-mocha-test to run our Mocha tests.

C:\kata\gruntHelloWorld>npm install grunt-mocha-test --save-dev


Now we'll initialize the configuration of Grunt by adding in a method called grunt.initConfig (which is an alias for grunt.config.init) to the function we are exporting.

module.exports = function(grunt){
  grunt.initConfig({
    mochaTest: {
      test: {
        options: {
          reporter: 'spec',
          clearRequireCache: true
        },
        src: ['test/*.js']
      },
    }
  });

  grunt.loadNpmTasks('grunt-mocha-test');

  grunt.registerTask('default', ['mochaTest']);
};

We call grunt.initConfig with an object containing a name property of mochaTest (you should be able to call it whatever you want), this property has an object with a property of test which in turn has options which is where we place our options to Mocha.  We set the reporter to use spec and to clear the require cache (I find this makes life a lot easier).  Next we load the npm tasks (this could have been done earlier) and lastly we register mochaTest as part of default to Grunt.

C:\kata\gruntHelloWorld>grunt


If we want we could register a task just for testing.

module.exports = function(grunt){
  grunt.initConfig({
    mochaTest: {
      test: {
        options: {
          reporter: 'spec',
          clearRequireCache: true
        },
        src: ['test/*.js']
      },
    }
  });

  grunt.loadNpmTasks('grunt-mocha-test');

  grunt.registerTask('default', ['mochaTest']);
  grunt.registerTask('test', ['mochaTest']);
};

Now we can call grunt with test and it will execute mochaTest task.

C:\kata\gruntHelloWorld>grunt test



Let's look at adding in continuous testing.

Adding in Watch


We'll now add a watcher which will call our mochaTest task on any changes to our files.

We'll add the plugin grunt-contrib-watch.

C:\kata\gruntHelloWorld>npm install grunt-contrib-watch --save-dev


We now add watch to our Gruntfile.js

module.exports = function(grunt){
  grunt.initConfig({
    mochaTest: {
      test: {
        options: {
          reporter: 'spec',
          clearRequireCache: true
        },
        src: ['test/*.js']
      },
    },

    watch: {
      js: {
        options: {
          spawn: true,
          interrupt: true,
          debounceDelay: 250,
        },
        files: ['Gruntfile.js', 'src/*.js', 'test/*.js'],
        tasks: ['mochaTest']
      }
    }
  });

  grunt.loadNpmTasks('grunt-mocha-test');
  grunt.loadNpmTasks('grunt-contrib-watch');

  grunt.registerTask('default', ['mochaTest']);
  grunt.registerTask('test', ['mochaTest']);
};

I find these options work best for me, but you may want to read the documentation to find what will work best for you.

The files property is where we say what files to watch in this case the Gruntfile.js and anything under our src and test folder which is a JavaScript file.

The task property says which task to run when the watch detects a change in the files it is watching, in this case we want to run our Mocha tests (and thus create a continuous testing environment).

Start up the watcher with grunt watch.

C:\kata\gruntHelloWorld>grunt watch


We are now ready for some continuous testing TDD goodness.

Hello World


Let's add in expect.js for testing our code (feel free to use whatever you prefer).

C:\kata\gruntHelloWorld>npm install expect.js --save-dev



(If you have been following along you'll need to Ctrl+c out of the watch first.)

We'll start off with the following in our newly created helloSpec.js file.

var sut = require("../src/hello"),
 expect = require("expect.js");

describe("Hello World using TDD", function(){
  it("Given nothing then Hello is returned", function(){
    expect(sut.helloer()).to.be.equal("Hello");
  });
});


Running the test we'll have something like the following.

C:\kata\gruntHelloWorld>grunt test


We can now create the hello.js file with the following to pass the test.

exports.helloer = function(){
  return "Hello";
}

We now can start the watch up and do some TDD.

C:\kata\gruntHelloWorld>grunt watch


Boom!  We now have a continuous testing environment set up!

We can now set up a failing test around adding in a name to say hello to.

We add the following test to helloSpec.js in our test folder and watch the watch detect the change and report the failure.

var sut = require("../src/hello"),
 expect = require("expect.js");

describe("Hello World using TDD", function(){
  it("Given nothing then Hello is returned", function(){
    expect(sut.helloer()).to.be.equal("Hello");
  }),
  it("Given Mike then Hello Mike is returned", function(){
    expect(sut.helloer("Mike")).to.be.equal("Hello Mike");
  });
});


Nice!  Now we can make it pass (this is what I've added to hello.js).

exports.helloer = function(name){
  var ret = "Hello";

  if (name) ret += " " + name;

  return ret;
}




Now we have a fully operational continuous testing environment.

Allude


Here is what we got now in terms of files.

package.json


{
  "name": "HelloWorld",
  "version": "0.0.1",
  "description": "A simple example of continuous testing using Grunt and Mocha.",
  "main": "hello.js",
  "directories": {
    "test": "test"
  },
  "scripts": {
    "test": "grunt"
  },
  "keywords": [
    "example",
    "grunt",
    "mocha",
    "hello",
    "world"
  ],
  "author": "Mike Harris",
  "license": "BSD-2-Clause",
  "devDependencies": {
    "grunt": "~0.4.2",
    "mocha": "~1.17.1",
    "grunt-mocha-test": "~0.9.0",
    "grunt-contrib-watch": "~0.5.3",
    "expect.js": "~0.2.0"
  }
}

Gruntfile.js


module.exports = function(grunt){
  grunt.initConfig({
    mochaTest: {
      test: {
        options: {
          reporter: 'spec',
          clearRequireCache: true
        },
        src: ['test/*.js']
      },
    },

    watch: {
      js: {
        options: {
          spawn: true,
          interrupt: true,
          debounceDelay: 250,
        },
        files: ['Gruntfile.js', 'src/*.js', 'test/*.js'],
        tasks: ['mochaTest']
      }
    }
  });

  grunt.loadNpmTasks('grunt-mocha-test');
  grunt.loadNpmTasks('grunt-contrib-watch');

  grunt.registerTask('default', ['mochaTest']);
  grunt.registerTask('test', ['mochaTest']);
};

src\hello.js


exports.helloer = function(name){
  var ret = "Hello";

  if (name) ret += " " + name;

  return ret;
}

test\helloSpec.js


var sut = require("../src/hello"),
 expect = require("expect.js");

describe("Hello World using TDD", function(){
  it("Given nothing then Hello is returned", function(){
    expect(sut.helloer()).to.be.equal("Hello");
  }),
  it("Given Mike then Hello Mike is returned", function(){
    expect(sut.helloer("Mike")).to.be.equal("Hello Mike");
  });
});