Last Updated: March 07, 2016
· mysterycommand

Yeoman + AMD (RequireJS) + Mocha/Chai

Assuming you like the setup I described here (and why wouldn't you), this will describe how to add Mocha and Chai for testing in a way that will work with RequireJS modules that live in your app/ directory.

Create a config.js file for use by both app/ and test/

  1. Split app/scripts/main.js file out into two files, app/scripts/main.js and app/scripts/config.js. The config.js file has main as a dependency, so main, app and bootstrap files are loaded as before.
  2. Setup index.html to point at config.js, and remove the usemin comments, like this. Note: the commented out script block pointing at scripts/app.min.js will be useful later, so be sure to include it.

Update test/ to use the generated CSS and testing libraries installed with Bower

  1. Install Mocha and Chai as dev dependencies:

    bower install mocha --save-dev
    bower install chai --save-dev

  2. Remove the test/lib directory.

  3. Replace the entire contents of test/index.html with the contents of app/index.html.

  4. In the head of the new test/index.html, add references to the Mocha and Chai libraries installed with Bower:

    <link rel="stylesheet" href="../app/bowercomponents/mocha/mocha.css">
    <script src="../app/bower
    <script src="../app/bower_components/chai/chai.js"></script>

  5. Update the reference to the main stylesheet, to point at the generated CSS file. The Gruntfile.js will be updated to serve .tmp and ., so the relative path will be ../styles/main.css.

Update test/ to use the shared config.js.

  1. At the bottom of test/index.html (right before the closing body tag), remove the Chrome Frame conditional comment, the Google Analytics script tag, and the commented out script block referencing scripts/app.min.js (it won't be needed for tests).
  2. Update the reference to RequireJS to point to ../app/bower_components/requirejs/require.js and set it's data-main attribute to spec/config.
  3. Create spec/config.js, something like this:

    'use strict';

    baseUrl: '../../app/scripts/', // non-pathed dependencies should come from the app/scripts directory
    deps: ['runner'], // load spec/runner.js by default
    paths: {
    spec: '../../test/spec', // path tests to this directory
    runner: '../../test/spec/runner', // the main test runner, load all tests as dependencies
    appConfig: '../../app/scripts/config' // the app's config file
    shim: {
    runner: ['appConfig'] // make runner depend on the app's config file

  4. Create spec/runner.js something like this:

    /* global define */
    // All your tests go here.
    'spec/app.test' // maybe it makes sense to add tests as dependencies in spec/config?
    ], function () {
    'use strict';

    window.console = window.console || function() {}; // protect from barfs
    window.notrack = true; // don't track
    window.mocha.run(); // kickoff the runner


  5. Create spec/app.test.js something like this:

    /* global define, describe, it, should */
    define(['app'], function (app) {
    'use strict';

    // whatever tests are in here will run as soon as this module is loaded
    describe('app', function () {
        it('should exist', function() {


Update Gruntfile.js to serve and test everything

Make these changes to Gruntfile.js. The most important of these are:

  1. Add mountFolder(connect, '.'), to the connect:livereload task. This adds the project root to files served at localhost:9000. Tests will be available when the server is running at localhost:9000/test.
  2. Replace mountFolder(connect, 'test') with mountFolder(connect, '.') in the connect:test task. This is required in order for test/index.html to have relative path access to app-level dependencies.
  3. Update the mocha:all task options to run: false and urls: ['http://localhost:<%= connect.options.port %>/test/index.html']. Optionally set the reporter option: reporter: 'Spec'. Run is now triggered by test/spec/runner.js when it (and it's dependencies) have loaded. The tests are now located in test/index.html because we are serving the project root.
  4. Update the requirejs:dist task by adding to the options hash:

    include: '../bower_components/requirejs/require',
    mainConfigFile: yeomanConfig.app + '/scripts/config.js',
    out: yeomanConfig.dist + '/scripts/app.min.js'

Nearly done!

If all went well, you should have passing tests at both http://localhost:9000/test (while running grunt server), and via the command line by running grunt test. Assuming that's true, commit whatever's not committed, and push up to GitHub. When the Travis hook fires, you should also have a successful build in your gh-pages branch!

But wait! What's this!?

The built files don't quite work yet. Because we're not using usemin to rewrite our script tags, dist/index.html has a script tag referencing bower_components/requirejs/require.js … but no such file exists in the dist directory.

However, that little commented out script tag referencing scripts/app.min.js does get updated to point to the generated (RequireJS included, and revd) JavaScript. So how do we get dist to use it?

Add cleanup.js, and update .travis.yml to use it in the after_success hook.

Here's app/scripts/util/cleanup.js in it's entirety (I'm not super familiar with Node, so if there's a better way to do this, please do tell). Also, please note: This script is really specific to how I have things set up. I accept no responsibility if this destroys your project and ruins your life. USE AT YOUR OWN RISK!:

'use strict';

var fs = require('fs');
var filename = process.argv[2];

// Make sure we got a filename on the command line.
if (process.argv.length < 3) {
    console.log('Usage: node ' + process.argv[1] + ' FILENAME');

// Read the file.
fs.readFile(filename, 'utf8', function(error, data) {
    var file = data;

    // Find the old RequireJS script tag, and remove it.
    var tag = '        <script data-main="scripts/config" src="bower_components/requirejs/require.js"></script>';
    var tagIndex = file.indexOf(tag);
    file = file.substring(0, tagIndex) + file.substring(tagIndex + tag.length + 1);

    // Assume the last comment in the HTML file is the one you want to remove.
    // TODO: Make it a comment like build:uncomment or build:remove like `usemin` does.
    var beginComment = file.lastIndexOf('<!-- ');
    file = file.substring(0, beginComment) + file.substring(beginComment + 5);

    var endComment = file.lastIndexOf(' -->');
    file = file.substring(0, endComment) + file.substring(endComment + 4);

    // Write the edited file back into place.
    fs.writeFile(filename, file, 'utf8', function(error) {
        if (error) { throw error; }

Create this file, then add the following line to .travis.yml between after line 45 (like here):

- node ../app/scripts/util/cleanup.js ../dist/index.html

This way, when Travis successfully builds your project, it will remove the script tag pointing at the Bower-loaded RequireJS library, and un-comment script tag pointing to the r.js optimized, and revd JavaScript.

This has been a really long Pro Tip, but I hope you find it useful. If anyone knows how to improve upon cleanup.js I'm all ears. Further, if there's a way to have Grunt run cleanup.js on dist/index.html during grunt:build that'd be good (better) too.

Also, I don't really know what's going on with this formatting … for a code-specific community to not get code formatting right is a pretty huge bummer. If you have any questions you can check out the whole, un-touched after this demo project here:


2 Responses
Add your response


when exactly was this published? there's been some recent shift in RequireJS support in Yeoman and i'm looking for the solution. is this recent?

over 1 year ago ·

@theOther, the last time I updated the repo was 7 months ago, but I've been using a setup very like it since then with only minor tweaks for Yeoman 1.0 … I don't know of any breaking changes in RequireJS.

Looks like the webapp generator removed RequireJS a couple months ago … you could probably just reverse engineer the RequireJS-enabled Gruntfile? I'll update this post as soon as I get a chance (it might not be that soon).

over 1 year ago ·