Skip to content

Instantly share code, notes, and snippets.

@michaelcox
Last active January 11, 2024 06:05
Show Gist options
  • Save michaelcox/3800736 to your computer and use it in GitHub Desktop.
Save michaelcox/3800736 to your computer and use it in GitHub Desktop.
Browser Unit Testing with Backbone Mocha Chai and RequireJS
<html>
<head>
<meta charset="utf-8"/>
<title>Backbone Tests</title>
<link rel="stylesheet" href="libs/mocha.css"/>
</head>
<body>
<div id="mocha"></div>
<script data-main="SpecRunner.js" src="/app/libs/require.js"></script>
</body>
</html>
define(function(require) {
var models = require('models');
describe('Models', function() {
describe('Sample Model', function() {
it('should default "urlRoot" property to "/api/samples"', function() {
var sample = new models.Sample();
sample.urlRoot.should.equal('/api/samples');
});
});
});
});
define(function(require) {
var Backbone = require('backbone');
var models = {};
models.Sample = Backbone.Model.extend({
urlRoot: '/api/samples'
});
return models;
});
require.config({
baseUrl: '/backbone-tests/',
paths: {
'jquery' : '/app/libs/jquery',
'underscore' : '/app/libs/underscore',
'backbone' : '/app/libs/backbone',
'mocha' : 'libs/mocha',
'chai' : 'libs/chai',
'chai-jquery' : 'libs/chai-jquery',
'models' : '/app/models'
},
shim: {
'chai-jquery': ['jquery', 'chai']
},
urlArgs: 'bust=' + (new Date()).getTime()
});
define(function(require) {
var chai = require('chai');
var mocha = require('mocha');
require('jquery');
require('chai-jquery');
// Chai
var should = chai.should();
chai.use(chaiJquery);
mocha.setup('bdd');
require([
'specs/model-tests.js',
], function(require) {
mocha.run();
});
});
@rbinksy
Copy link

rbinksy commented Mar 24, 2013

Thanks for this! are there any differences for using 'expect' over 'should' in chai? or is it down to personal preference?

@thaddeusalbers
Copy link

@AlexZeitler
Copy link

Thanks for sharing. I'm using a similar structure which works fine inside the browser but it fails when using mocha-phantomjs (http://stackoverflow.com/questions/15657458/mocha-init-timeout-with-mocha-phantomjs). Did you ever test your solution with mocha-phantomjs? (https://github.com/metaskills/mocha-phantomjs)

@phillycoder
Copy link

Thanks for this gist, very useful.

I had to add a line after setup, to let mocha continue run tests after first fail.

  mocha.setup('bdd');
  mocha.bail(false);

@kumarharsh
Copy link

@craigrich : Check out this page for the differences between expect & should. http://chaijs.com/guide/styles/

@kumarharsh
Copy link

+1 for this gist. Awesome!

@rwestgeest
Copy link

Thanks for sharing. I have searched for hours for such a clear example. Found lots. This one really helped.

@AlexZeitler To run with mocha-phantomjs:

change:

    mocha.run();

to

    if (window.mochaPhantomJS) {
      mochaPhantomJS.run();
    }
    else {
      mocha.run();
    }

in SpecRunner.js

to use with expect i had to add th following line to SpecRunner.js

expect = chai.expect;  // note no 'var'

near

var should = chai.should();

(actually, I replaced that line.

@mnoble01
Copy link

mnoble01 commented Aug 6, 2013

@rwestgeest, thanks so much! I was searching for a solution to @AlexZeitler's same problem for a couple hours.

@kucherenko
Copy link

Thank you, very useful gist.
Is it possible to run this tests with mocha command in terminal without phantomjs?

@htulipe
Copy link

htulipe commented Sep 8, 2013

Very useful, thanks a lot

@chrishokamp
Copy link

Thanks for this great gist!

I also had to add shims for mocha and chai to get this to work via grunt test:

shim: {
    ...,
    mocha: {
         exports: 'mocha'
    },
    chai: {
         exports: 'chai'
    }
},

@gmfx
Copy link

gmfx commented Dec 11, 2013

Thanks @rwestgeest got tripped by same issue head banging for a little while there..

@dminkovsky
Copy link

Coolness, thank you.

@The1nternet
Copy link

Would someone be kind enough to explain how var should = chai.should() works when using RequireJS like in this example? Using "var" will scope "should" to within that single require function, therefore making it not available in the test fixtures. When I follow the example, it does not make "should" available in my test fixtures. Instead, i have to use the alias Should(), and I have to assign it to the window object like this: window.Should = chai.Should()
How are all of you able to get "should" to work in your tests without doing what i just described?
When i do this then, in my tests, i have to use Should, instead of should...
Here's a little explanation of why you have to do this, if you're making "should" global: chaijs/chai#86
and if you're NOT making it global, then how is it available in your tests?

@steeren
Copy link

steeren commented Aug 20, 2014

@michaelcox, great work, thank you - very useful.
Just one correction. The filename model-test.js doesn't match the actual filename (model-tests.js).

In specRunner.js it should read:
require([
'specs/model-tests.js',
],

Also, one tiny tiny little thing (not really a bug but more for your information), your require call, requires require :-) Clearly if require is already in scope (its global) then your require call, really shouldn't be requiring itself.
This would be more correct:
require(['chai', 'chai-jquery', 'mocha'], function(chai, chaiJquery, mocha){

// Init Chai
chai.should(); //initializes chai.should()
chai.use(chaiJquery);

/*globals mocha */
mocha.setup('bdd');

require([
'specs/model-test.js',
], function(require) {
mocha.run();
});

});
note:
we require mocha in the block above, but what is passed in is wrong. This can be corrected by adding this to the shim section:
shim: {
'mocha': { exports: 'mocha'},

And finally....
@The1nernet,
The line:
// Chai
var should = chai.should();

Is indeed misleading and I can't explain the rational behind it, but I can tell you that the magic occurs because the first time you call chai.should() the should() function is defined and initialised (it always helps to read/debug through these libraries - treating any JS library like a blackbox is a mistake - in almost all cases.
For me things worked after I changed the filename bug, and I simply changed the chai.should() line above to be a tiny bit more helpful to me:
// Initialize the Chai.should() function
chai.should();

Still though, it is weird and understandable that it caused confusion.

@michaelcox
Copy link
Author

Thanks @steeren - it's been a while since I wrote this, but I fixed the two things you pointed out. Yes I wouldn't need to require "require", and the link to model-tests.js was incorrect.

For the "mocha" require, however, I think it should work without a shim. The latest version of mocha at least has support for requirejs.

@michaelcox
Copy link
Author

A link to the original blog post as well as some additional thoughts on unit testing in backbone can be found here:

http://mdcox.net/posts/backbone-unit-testing.html

@indigofeather
Copy link

Very useful, thanks a lot

@danascheider
Copy link

I've been looking for something like this for weeks, thank you so much!

@sfahlberg
Copy link

I'm having difficulty loading mocha (it says: "Uncaught TypeError: Cannot read property 'setup' of undefined"). The reason is because the variable "mocha" is undefined. I downloaded mocha.js from https://github.com/mochajs/mocha/blob/master/mocha.js but for some reason that doesn't seem to be the right code. I looked online and everyone says to use npm and nobody mentions copying. Can someone clarify?

@danascheider
Copy link

sfahlberg - take a look and see if my gist helps you. I changed a few things from this one because I had a some problems on account of the fact that I didn't want my index.html to consist of the test code, but I remember I also had problems like the one you're describing and they are gone now.

https://gist.github.com/danascheider/82eda70a4f7152841dca

Let me know if this helps.

@danascheider
Copy link

And michaelcox, I just wanted to thank you again for this gist, I have been testing successfully for weeks now and honestly don't know if I would have gotten to this point without it. You added some much-needed clarity to a hair-pulling process.

@Tallowman
Copy link

@sfahlberg - I have had the same problem and have fixed it by simply not assigning require('mocha') to var mocha ie, this line:
var mocha = require('mocha');
becomes
require('mocha');

I am very new to TDD and require, and I'm not really sure why this works. I also copied the code, but from here, the link I found on another blog post ( it looks like the same code ):
https://github.com/visionmedia/mocha/raw/master/mocha.js

If anyone can tell me why I've had to apply this fix, or if you have found a solution sfalhberg, to making it work with the copied code I would be most grateful :)

@mike-kelly
Copy link

@sfahlberg - I had the same issue. This is how I got it to work:

requirejs.config({
    baseUrl: '../client/js/',
    paths: {
        'jquery'        : 'libs/jquery',
        'underscore'    : 'libs/underscore',
        'backbone'      : 'libs/backbone',
        'mocha'         : '../../tests/libs/mocha',
        'chai'          : '../../tests/libs/chai',
        'chai-jquery'   : '../../tests/libs/chai-jquery',
        'cardmodels'    : 'app/models/cardmodels'
    },
    shim: {
        'chai-jquery': ['jquery', 'chai'],
        'mocha': {
            init: function () {
                this.mocha.setup('bdd');
                return this.mocha;
            }
        },
        'backbone': {
            deps: ['underscore', 'jquery'],
            exports: 'Backbone'
        },
        'underscore': {
            exports: '_'
        }
    },
    urlArgs: 'bust=' + (new Date()).getTime()
});

define([
    'chai',
    'chai-jquery',
    'mocha'
],

function (chai, chaiJquery, mocha) {

    // Chai
    var should = chai.should();
    chai.use(chaiJquery);

    require([
        'specs/cardmodels-tests.js',
    ], function(require) {
        mocha.run();
    });
});

See in particular the shims section. The mocha shim which does mocha.setup was what got mocha working for me.

I also had to shim Backbone to get it working with the actual tests.

Also note that my paths are different, as I've set this up to run from a tests folder in my working app. The index.html file is inside tests

@michaelcox - in your gist chaiJquery is undefined since the recent file revision

@mikealexander
Copy link

Thank-you @michaelcox and @mike-kelly. Got my tests up and running after a frustrating couple of hours.

@leftclickben
Copy link

Thanks for this, very useful. In my scenario, I wanted the tests to run both under gulp-mocha-requirejs and in the browser. This is what the core of my equivalent to SpecRunner.js looks like:

require.config({
    paths: {
        mocha: '../node_modules/mocha/mocha',
        chai: '../node_modules/chai/chai',
        // ... my modules ...
    },
    shim: {
        mocha: {
            init: function () {
                return this.mocha.setup({
                    ui: 'bdd',
                    reporter: /phantom/i.test(window.navigator.userAgent) ? 'spec' : 'html'
                });
            }
        }
    }
});

require(
    [
        'mocha'
    ],
    function (mocha) {
        require(
            [
                './spec/unit/main.spec'
            ],
            function () {
                mocha.run();
            }
        );
    }
);

The userAgent switch feels a bit hacky but it works.

@amitrai99
Copy link

Great gist, thanks for this.
Is there a way to run a single test using this method?

@maryjanebarger
Copy link

Can someone post their Gruntfile.js or gulpfile.js used to run this code? Thanks.

@bebnev-pro
Copy link

Thank you very much, man. It's really help me to add unit testing in my test-app based on old backbone... Guys, get attention that baseUrl is very important for all your future imports and paths. It will be based on your .html run-test file in your file system. Other thing - very great and really helpfull.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment