Running an Express server with Grunt and Yeoman: Part 3

Back in part 1 of this series I introduced a small number of tools that make a web engineer’s life easier: Grunt, Yeoman, Express, and Nodemon. In part 2 I provided a repository of files and explained how to use them to have a running server that’s capable of automatic browser and app server reboots for rapid development.

Now, in part 3, I’ll explain how we can use this workflow to automatically run unit tests when development files or test files change, backend or frontend, and how to expose internal functions and properties in our Node or RequireJS modules to the testing environment so that we get less bugs. Onward!

The Skinny

Our repository has been outfitted with Karma, Mocha, Chai, Sinon, Sinon-Chai, and Istanbul. In our starter set of files in the repository, a neat set of examples have been written to familiarize you with how they can fit together in your project. In addition, Grunt tasks have been included to leverage these tools to run your unit tests and code coverage whenever your files change.

You don’t need to do anything more than create test files in the test/ directory that end in .spec.js, create client-side files in app/scripts/app/, and server-side files in lib/. In the coverage/ directory you’ll find .html files showing just how thorough and robust your tests really are. This’ll give you a greater level of confidence in your work now and in the future.

Special Notes

There’s an issue in the grunt-contrib-watch task where if two targets are watching the same file(s), only the last defined target will run if the file(s) change. Right now this means client-side tests will run and livereload will not when client-side scripts are saved. You can switch this behavior by altering the watch target order in Gruntfile.js. Watch grunt-contrib-watch issue #25 and upgrade your local copy of that library using npm when it’s fixed. The grunt-contrib-watch task has been updated and grunt-express-workflow has been updated to include the fix! Thanks to the grunt-contrib-watch developers for a fantastic tool.

Since your RequireJS client-side development environment, grunt build environment, and test environment are by necessity all different, it’s important that you maintain each environment’s corresponding RequireJS configuration file. These are found in app/scripts/app/config.jsGruntfile.js under the requirejs task, and test/frontend/app/config.js, respectively.

You can configure the grunt-karma task to use multiple browsers for testing. Adding 'PhantomJS' as a test environment gave me issues, but they turned out to be that my version of node was outdated and Chai has an issue with should on PhantomJS. These can be fixed by running Node >= v0.10.12 and with the shim I included in test/frontend/app/config.js.

Running Tests Manually

The grunt test command is configured to run your frontend and backend tasks with code coverage. This can be changed easily to no coverage in the Gruntfile. The grunt build command is configured to run these tests and output a production build of your application if they pass.

You can run tests for either your frontend or backend files right from the command-line. If you’d like to run your backend tests with coverage, use grunt backendCoverage; without coverage, use grunt simplemocha:backend. This command is significantly faster.

For the frontend, the command for running tests with coverage is grunt karma:appContinuous. “Continuous” signifies that the test run is also a single-run, not a long-running process. For a test run without coverage, use grunt karma:appContinuousNoCoverage. The karma:app and karma:appNoCoverage tasks are long-running processes that are meant to be run alongside the watch task if desired. The karma:app task is configured this way by default, with karma:app:run declared under the watch task, taking advantage of the Karma server started by karma:app in grunt server.

By modifying the watch, grunt server, grunt build, and grunt test tasks in the Gruntfile, you can alter what types of tests run and how.

What is Istanbul and what is code coverage?

Istanbul is a fairly new tool that will give you insight into how well your test cases are covering your production code. It “instruments” your files and when the tests are run, records every time a function, statement, branch, etc. is executed. You’ll know where you may need to aim future tests. Here’s an example of what an Istanbul report looks like.

Running Server-side Node.js Code in a test Environment

Server-side tests run with the global node.js variable process.env.NODE_ENV set to 'test'. You can take advantage of this in your server-side code by exporting more variables or functions for testing when a test for that is true, like so:

function externalFunction (value) {}

function internalFunction (value) {
  return value + 2;
}

// Module Exports
exports.externalFunction = externalFunction;

// this allows you to test the internalFunction in your unit tests
if (process.env.NODE_ENV === 'test') {
  exports.internalFunction = internalFunction;
}

Exposing Internal Functions and Properties in RequireJS Modules to your Tests

Exposing internal functions and variables in RequireJS apps to your test environment is a little more complicated. Since RequireJS modules export their returned value whenever they’re require()d, we’ll need some way to export something different when they’re require()d in a different context. Has.js to the rescue!

By including has.js in your development environment and including a has property in your Gruntfile.js for the requirejs build task, all you have to do is register a global environment variable with has.add() and a has() test in your module, and when your module is require()d, it can potentially return something completely different than it normally would. Anything you want. When you run grunt build, that entire has() branch in your module is removed, leaving only your production code. Let’s review a few files to get a better picture of what’s going on.

// views/index.jade

/* ... */

block append bottom-script
  - if (dev)
    //- has.js is used to branch requirejs modules' returned values in a test environment like mocha.
        since the modules themselves contain has() tests, we need to include them in the dev environment.
        during a build, r.js will optimize out the has() tests, so has.js is not needed in production.
    script(src='/components/has/has.js')
    script(src='/components/requirejs/require.js', data-main='/scripts/app/config')
  - else
    script(src='/scripts/app/main.js')
// app/scripts/app/someModule.js

define([

],

function () {
  'use strict';

  // a function to illustrate how to test
  // the internals of RequireJS modules
  function internalFunction (data) {
    if (data) {
      return 1;
    } else {
      return 0;
    }
  }

  // a function that's normally returned
  // when this module is require()d
  function externalFunction () {
    return true;
  }

  // expose anything you want to the test environment.
  // you can even choose to return the module's normal value by using `has` within a test suite.
  // this entire branch will be removed automatically during a production build.
  if (has('internalTest')) {
    return {
      externalFunction: externalFunction,
      internalFunction: internalFunction,
    };
  }

  // otherwise return the module's normal value
  return externalFunction;
});
// Gruntfile.js

/* ... */

requirejs: {
  /* ... */
  app: {
    /* ... */
    has: {
      // a has() assignment only for unit testing. RequireJS modules are
      // able to return different values when unit tests set
      // this variable to true
      internalTest: false,
    }
  }
},

/* ... */
// test/frontend/app/someModule.spec.js

define([
  // this will give us the normal return value for the module
  'someModule'
],

function(someModule) {
  /*
   * External Module Tests:
   *   tests using only the normal return value from the module itself
   */
  describe('someModule external tests', function() {
    it('should return true', function() {
      expect(someModule).to.be.a('function');
      expect(someModule()).to.equal(true);
    });
  });

  /*
   * Internal Module Tests:
   *   tests using the internal functions and properties of the module
   */
  describe('someModule internal tests', function () {
    // placeholder variable for when we require() the module in a different context
    var someModule;

    // this is run once, before any tests in this suite take place
    before(function (done) {
      // set internalTest environment so that the
      // someModule module returns internal functions for testing
      has.add('internalTest', function () {
        return true;
      }, true);

      // remove the module from the requirejs cache so that we
      // can require() it again with the internalTest environment set
      require.undef('someModule');

      require(['someModule'], function (module) {
        someModule = module;
        done();
      });
    });

    it('should return an object', function() {
      expect(someModule).to.be.an('object');
    });

    it('should test internalFunction with no args', function () {
      expect(someModule).to.have.property('internalFunction');

      var result = someModule.internalFunction();
      expect(result).to.equal(0);
    });

    it('should test internalFunction with one argument', function () {
      expect(someModule).to.have.property('internalFunction');

      var result = someModule.internalFunction({ test: 'data'});
      expect(result).to.equal(1);
    });
  });
});

And that’s how we can make our RequireJS tests a lot more robust!

Expanding to More Namespaced Client-Side Apps

The folder structure and Gruntfile.js are organized in such a way that the main client-side app lives in app/scripts/app/ . This allows us to make other client-side apps that use RequireJS but that don’t interact with and are separate from the app that lives in app/scripts/app/ . For instance, if you have a single-page app with its own modules and want to create another single-page app for another part of your site with its own modules, it’s possible to do so by creating another namespace for it in, say, app/scripts/otherApp/ . Now all you need to do is set it up with Grunt so that it’ll watch files for changes and run your tests. You’ll need to:

It sounds complicated, but once you get started it’ll make a lot more sense, for sure.

Happy hacking! :)

Comments