Blanket + Mocha with RequireJS (and Grunt)

I spent a lot of time digging recently to figure out how to setup a complete solution for unit testing and code coverage with Mocha both in the browser as well as through grunt-cli. There are a lot of great resources out there and it was easy to get one piece or another working, but the challenge came in tying them all together. Wanted to put this out there to hopefully save someone from the challenges I faced.

Mocha and Blanket in the Browser

TL;DR

For those of you who are more inclined to cherry-pick a repo, here's what you need.

Structure

The structure of this is pretty simple, I'm assuming this will be built into a dist directory, so I have a /src folder in root where dev work will be done and a /test folder for my unit tests and coverage. Pretty simple.

The other thing worth noting is that in my /src/js folder I am loading my bower components into a subdirectory vendor

Dependencies

I'm using the following stack across npm and bower:

NPM

My package.json loads the usual suspects, additionally, for this exercise, I'm using grunt-blanket-mocha (v.0.3.3). That's really all you need beyond having grunt-cli and any other dep's your app requires.

BOWER

Again, few dependencies for this specific task:

The Setup

For easy reading I'm going to bullet-list this one:

  • I setup a basic script at /src/js/components/example.js which will be loaded through RequireJS. This is the script I'm testing
  • In /test I created and index.html for running my tests a specs folder with a test for the example.js script in my src/js/components and a main-tests.js file for initiating in my data-main attr on the RequireJS script call.
  • Tests are being added to the main-tests runner through /test/specs.js which then loads them from /test/specs
  • I've created a /src/js/require-config.js file to hold the main RequireJS paths and setup so it can be utilized in both /src and /test

The Test Runner - main-tests.js

This is actually pretty simple, just call in my require-config, do some overrides and additions to the paths and setup my test runner:

/* global mocha */
require(["../src/js/require-config"], function () {

    require.config({
        baseUrl: "../src/js",
        paths: {
            chai: "vendor/chai/chai"
        }
    });

    require([
        "chai"
    ], function (chai) {
        chai.should();
        window.expect = chai.expect;
        mocha.setup("bdd");

        require([
            "specs.js"
        ], function () {
            mocha.run();
        });
    });

});

The Page - index.html

This gets a bit trickier. What I found was that more important than anything was the order in which dependencies and scripts were loaded so the dependency chain was satisfied:

<!DOCTYPE html>  
<html>  
<head>  
    <meta charset="utf-8">
    <title>Tests</title>
    <link rel="stylesheet" href="../src/js/vendor/mocha/mocha.css">

</head>  
<body>  
    <div id="mocha"></div>

    <script src="../src/js/vendor/mocha/mocha.js"></script>

    <script data-main="main-tests" src="../src/js/vendor/requirejs/require.js"></script>

    <script src="../src/js/vendor/blanket/dist/qunit/blanket.js" data-cover-only="../src/js/component"></script>
    <script type="text/javascript" src="../node_modules/grunt-blanket-mocha/support/mocha-blanket.js"></script>

    <script>
    /* global blanket */
    if (window.PHANTOMJS) {
        blanket.options("reporter", "../node_modules/grunt-blanket-mocha/support/grunt-reporter.js");
    }
    </script>

    <style>
        #blanket-main { margin: 0 25px 0 65px; font-size: 12px }
    </style>
</body>  
</html>  

A few things of note:

  • I setup my <div id="mocha"></div> first so it's available
  • I then load the Mocha script
  • Next, I load requirejs with data-main reference to my test runner
  • Only after Mocha and RequireJS are setup do I load in Blanket followed by the mocha-blanket adapter
  • Lastly I have the if (window.PHANTOMJS) ... call which changes the reporter for blanket when running via the grunt-cli

In addition to the above, I also got picky and created a small override on the #blanket-main styles. You can modify this or completely omit it, however, if you want to style the Blanket output this needs to be the very last thing on the page.

Grunt Configuration

Since the end goal of any project should be continuous itegration (and deployment) I want grunt to be able to run this as well via PhantomJS. The setup for this is very simple with the grunt-blanket-mocha component by placing the following in your gruntfile:

blanket_mocha: {  
    all: ["test/**/*.html"],
    options: {
        threshold: 90,
        run: false
    }
}

Then ensure the task runner is loaded via:

grunt.loadNpmTasks("grunt-blanket-mocha");  

That's all there is to it. The reference will run PhantomJS on the path specified and return the parsed results back to grunt for output with the proper status codes.

Conclusion

Most of us know the importance of unit testing, but I don't see much on code-coverage in the JS community, which I personally feel leaves unit testing as a much less effective tool as there's no guaruntee you're fully testing your code. Like much else we encounter there are many tools and ways to handle coverage, this is simply how I approach it.


comments powered by Disqus