11

I'm trying to get tests written with Mocha to work running Karma, and they sort of work, but I cannot use the done() method to implement async tests, which essentially makes the tools useless to me. What am I missing?

karma.conf.js

module.exports = function(config) {
  config.set({
    basePath: '../..',
    frameworks: ['mocha', 'requirejs', 'qunit'],
    client: {
        mocha: {
            ui: 'bdd'
        }
    },
    files: [
      {pattern: 'libs/**/*.js', included: false},
      {pattern: 'src/**/*.js', included: false},
      {pattern: 'tests/mocha/mocha.js', included: false},
      {pattern: 'tests/should/should.js', included: false},
      {pattern: 'tests/**/*Spec.js', included: false},
      'tests/karma/test-main.js'
    ],
    exclude: [
      'src/main.js'
    ],
    // test results reporter to use
    // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
    reporters: ['progress', 'dots'],
    port: 9876,
    colors: true,
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_WARN,
    autoWatch: true,
    // Start these browsers, currently available:
    // - Chrome
    // - ChromeCanary
    // - Firefox
    // - Opera (has to be installed with `npm install karma-opera-launcher`)
    // - Safari (only Mac; has to be installed with `npm install karma-safari-launcher`)
    // - PhantomJS
    // - IE (only Windows; has to be installed with `npm install karma-ie-launcher`)
    browsers: ['Chrome'],
    // If browser does not capture in given timeout [ms], kill it
    captureTimeout: 60000,
    // Continuous Integration mode
    // if true, it capture browsers, run tests and exit
    singleRun: false
  });
};

test-main.js (configuring RequireJS)

var allTestFiles = [];
var pathToModule = function(path) {
  return path.replace(/^\/base\//, '../').replace(/\.js$/, '');
};

Object.keys(window.__karma__.files).forEach(function(file) {
  if (/Spec\.js$/.test(file)) {
    // Normalize paths to RequireJS module names.
    allTestFiles.push(pathToModule(file));
  }
});

require.config({
  // Karma serves files under /base, which is the basePath from your config file
  baseUrl: '/base/src',
  paths: {
    'should': '../tests/should/should',
    'mocha': '../tests/mocha/mocha',
    'pubsub': '../libs/pubsub/pubsub',
    'jquery': '../libs/jquery/jquery-1.10.2',
    'jquery-mobile': '//code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min'
  },
  // dynamically load all test files
  deps: allTestFiles, 
  // we have to kickoff jasmine, as it is asynchronous
  callback: window.__karma__.start
});

tests/fooSpec.js

define(['music/note'], function(Note) {

describe('nothing', function(done) {
    it('a silly test', function() {
        var note = new Note;
        note.should.not.eql(32);
    });
    done();
});
...

Though this is a contrived example, it succeeds if I remove the done() call. As it is, I get:

Uncaught TypeError: undefined is not a function
at /Library/WebServer/Documents/vg/tests/mocha/fooSpec.js:8

This is the done() line. How/why is this not defined? I'm not understanding where else to configure Mocha (or with what options). Is there some sort of global namespace or meta-programming magic causing RequireJS to interfere with Mocha?

I'm running the tests in Chrome 33 on OS X 10.9.2, in case that is at all relevant. I've killed a ton of time on this and am ready to give up on automated testing :( -- had similar brick walls with QUnit/Karma/RequireJS and have not been able to find any alternative to successfully automate tests. I feel like an idiot.

Sergio
  • 28,539
  • 11
  • 85
  • 132
Jason Boyd
  • 1,192
  • 1
  • 9
  • 19

3 Answers3

17

In Mocha, the done callback is for it, before, after, beforeEach, afterEach. So:

describe('nothing', function() {
    it('a silly test', function(done) {
        var note = new Note;
        note.should.not.eql(32);
        done();
    });
});

Here's the doc.

Johannes Jander
  • 4,974
  • 2
  • 31
  • 46
Louis
  • 146,715
  • 28
  • 274
  • 320
2

The test you are running in that example doesn't require the done() callback. It is not asynchronous. An example of when the done callback is need....

describe('Note', function() {
    it('can be retrieved from database', function(done) {
        var note = new Note();
        cb = function(){
           note.contents.should.eql("stuff retrieved from database");
           done()
        }
        //cb is passed into the async function to be called when it's finished
        note.retrieveFromDatabaseAsync(cb)
    });
});

Your test should not have a done callback

describe('nothing', function() {
    it('umm...', function() {
        var note = new Note;
        note.should.not.eql(32);
    });

});

Only the 'it' function provides a done callback. describe does not. Your problem does not rest with karma. Your mocha tests are not defined correctly.

startswithaj
  • 344
  • 1
  • 9
  • I realize that. I had only been experimenting with the API before writing any real tests, and so was surprised at first that done wasn't defined. I was even more surprised when it *appeared* to become defined if I made my test async. It's all good. I've got it sorted out, written lots of tests. – Jason Boyd Mar 21 '14 at 04:29
1

Holy %$#@!

I would never in a million years have thought this would barf:

describe('nothing', function(done) {
    it('umm...', function() {
        var note = new Note;
        note.should.not.eql(32);
    });
    done(); // throws error that undefined is not a function
});

But this works just fine:

describe('nothing', function(done) {
    it('umm...', function() {
        var note = new Note;
        note.should.not.eql(32);
    });
    setTimeout(function() {
        done();  // MAGIC == EVIL.
    }, 1000);
});
Jason Boyd
  • 1,192
  • 1
  • 9
  • 19
  • 1
    So, I can't close my own question for a couple days. But maybe I never will. Maybe this question should *never* be marked answered. Thanks for the day or so I'll never get back; please add some docs explaining these shenanigans for the next poor sod. – Jason Boyd Mar 19 '14 at 02:35
  • I suspect the version you say works fine actually generates an exception that ends up being swallowed by something. It is certainly the case that by the time it executes Mocha won't care. – Louis Mar 19 '14 at 10:51
  • Okay, but it works as documented. The test passes/fails as expected. Are you suggesting that the implementation is to not pass anything to it(), before(), etc. and to asynchronously (only) catch exceptions if the client code has called undefined() ? – Jason Boyd Mar 19 '14 at 17:01
  • I've verified that it does exactly that. Or, that is, it silently catches errors thrown asynchronously (after the test case has returned), which has the net effect that you can use done() for describe() cases, or at least appear to. I'm not sure this is desirable behavior... – Jason Boyd Mar 19 '14 at 17:06
  • Did you look at the answer I wrote? – Louis Mar 19 '14 at 22:27
  • I'll mark your answer as answering, because you're correct, but honestly I think the question as to why Mocha is designed to behave this way is the salient one. Not that I'm complaining -- having gotten it all working I'm in testing heaven. Thanks @Louis. – Jason Boyd Mar 20 '14 at 03:04