Is there a way to get Chai working with asynchronous Mocha tests?

JavascriptUnit Testingmocha.js

Javascript Problem Overview


I'm running some asynchronous tests in Mocha using the Browser Runner and I'm trying to use Chai's expect style assertions:

window.expect = chai.expect;
describe('my test', function() {
  it('should do something', function (done) {
    setTimeout(function () {
      expect(true).to.equal(false);
    }, 100);
  }
}

This doesn't give me the normal failed assertion message, instead I get:

Error: the string "Uncaught AssertionError: expected true to equal false" was thrown, throw an Error :)
    at Runner.fail (http://localhost:8000/tests/integration/mocha/vendor/mocha.js:3475:11)
    at Runner.uncaught (http://localhost:8000/tests/integration/mocha/vendor/mocha.js:3748:8)
    at uncaught (http://localhost:8000/tests/integration/mocha/vendor/mocha.js:3778:10)

So it's obviously catching the error, it's just not displaying it correctly. Any ideas how to do this? I guess I could just call "done" with an error object but then I lose all the elegance of something like Chai and it becomes very clunky...

Javascript Solutions


Solution 1 - Javascript

Your asynchronous test generates an exception, on failed expect()ations, that cannot be captured by it() because the exception is thrown outside of it()'s scope.

The captured exception that you see displayed is captured using process.on('uncaughtException') under node or using window.onerror() in the browser.

To fix this issue, you need to capture the exception within the asynchronous function called by setTimeout() in order to call done() with the exception as the first parameter. You also need to call done() with no parameter to indicate success, otherwise mocha would report a timeout error because your test function would never have signaled that it was done:

window.expect = chai.expect;

describe( 'my test', function() {
  it( 'should do something', function ( done ) {
    // done() is provided by it() to indicate asynchronous completion
    // call done() with no parameter to indicate that it() is done() and successful
    // or with an error to indicate that it() failed
    setTimeout( function () {
      // Called from the event loop, not it()
      // So only the event loop could capture uncaught exceptions from here
      try {
        expect( true ).to.equal( false );
        done(); // success: call done with no parameter to indicate that it() is done()
      } catch( e ) {
        done( e ); // failure: call done with an error Object to indicate that it() failed
      }
    }, 100 );
    // returns immediately after setting timeout
    // so it() can no longer catch exception happening asynchronously
  }
}

Doing so on all your test cases is annoying and not DRY so you might want to provide a function to do this for you. Let's call this function check():

function check( done, f ) {
  try {
    f();
    done();
  } catch( e ) {
    done( e );
  }
}

With check() you can now rewrite your asynchronous tests as follows:

window.expect = chai.expect;

describe( 'my test', function() {
  it( 'should do something', function( done ) {
    setTimeout( function () {
      check( done, function() {
        expect( true ).to.equal( false );
      } );
    }, 100 );
  }
}

Solution 2 - Javascript

Here are my passing tests for ES6/ES2015 promises and ES7/ES2016 async/await. Hope this provides a nice updated answer for anyone researching this topic:

import { expect } from 'chai'

describe('Mocha', () => {
  it('works synchronously', () => {
    expect(true).to.equal(true)
  })

  it('works ansyncronously', done => {
    setTimeout(() => {
      expect(true).to.equal(true)
      done()
    }, 4)
  })

  it('throws errors synchronously', () => {
    return true
    throw new Error('it works')
  })

  it('throws errors ansyncronously', done => {
    setTimeout(() => {
      return done()
      done(new Error('it works'))
    }, 4)
  })

  it('uses promises', () => {
    var testPromise = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('Hello')
      }, 4)
    })

    testPromise.then(result => {
      expect(result).to.equal('Hello')
    }, reason => {
      throw new Error(reason)
    })
  })

  it('uses es7 async/await', async (done) => {
    const testPromise = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('Hello')
      }, 4)
    })

    try {
      const result = await testPromise
      expect(result).to.equal('Hello')
      done()
    } catch(err) {
      done(err)
    }
  })

  /*
  *  Higher-order function for use with async/await (last test)
  */
  const mochaAsync = fn => {
    return async (done) => {
      try {
        await fn()
        done()
      } catch (err) {
        done(err)
      }
    }
  }

  it('uses a higher order function wrap around async', mochaAsync(async () => {
    const testPromise = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('Hello')
      }, 4)
    })

    expect(await testPromise).to.equal('Hello')
  }))
})

Solution 3 - Javascript

If you like promised, try Chai as Promised + Q, which allow something like this:

doSomethingAsync().should.eventually.equal("foo").notify(done);

Solution 4 - Javascript

I asked the same thing in the Mocha mailing list. They basically told me this : to write asynchronous test with Mocha and Chai :

  • always start the test with if (err) done(err);
  • always end the test with done().

It solved my problem, and didn't change a single line of my code in-between (Chai expectations amongst other). The setTimout is not the way to do async tests.

Here's the link to the discussion in the mailing list.

Solution 5 - Javascript

I've published a package that resolves this issue.

First install the check-chai package:

npm install --save check-chai

Then in your tests, use chai.use(checkChai); and then use the chai.check helper function as shown below:

var chai = require('chai');
var dirtyChai = require('dirty-chai');
var checkChai = require('check-chai');
var expect = chai.expect;
chai.use(dirtyChai);
chai.use(checkChai);

describe('test', function() {

  it('should do something', function(done) {

    // imagine you have some API call here
    // and it returns (err, res, body)
    var err = null;
    var res = {};
    var body = {};

    chai.check(done, function() {
      expect(err).to.be.a('null');
      expect(res).to.be.an('object');
      expect(body).to.be.an('object');
    });

  });

});

Per <https://stackoverflow.com/questions/11235815/is-there-a-way-to-get-chai-working-with-asynchronous-mocha-tests/15208067#comment55687475_15208067> I published this as an NPM package.

Please see <https://github.com/niftylettuce/check-chai> for more information.

Solution 6 - Javascript

Try chaiAsPromised! Aside from being excellently named, you can use statements like:

expect(asyncToResultingValue()).to.eventually.equal(true)

Can confirm, works very well for Mocha + Chai.

https://github.com/domenic/chai-as-promised

Solution 7 - Javascript

Very much related to and inspired by Jean Vincent's answer, we employ a helper function similar to his check function, but we call it eventually instead (this helps it match up with the naming conventions of chai-as-promised). It returns a function that takes any number of arguments and passes them to the original callback. This helps eliminate an extra nested function block in your tests and allows you to handle any type of async callback. Here it is written in ES2015:

function eventually(done, fn) {
  return (...args) => {
    try {
      fn(...args);
      done();
    } catch (err) {
      done(err);
    }
  };
};

Example Usage:

describe("my async test", function() {
  it("should fail", function(done) {
    setTimeout(eventually(done, (param1, param2) => {
      assert.equal(param1, "foo");   // this should pass
      assert.equal(param2, "bogus"); // this should fail
    }), 100, "foo", "bar");
  });
});

Solution 8 - Javascript

I know there are many repeat answers and suggested packages to solve this however I haven't seen the simple solutions above offer a concise pattern for the two use cases. I am posting this as a consolidated answer for other who wish to copy-pasta:

event callbacks

function expectEventCallback(done, fn) {
  return function() {
    try { fn(...arguments); }
    catch(error) { return done(error); }
    done();
  };
}

node style callbacks

function expectNodeCallback(done, fn) {
  return function(err, ...args) {
    if (err) { return done(err); }
    try { fn(...args); }
    catch(error) { return done(error); }
    done();
  };
}

example usage

it('handles event callbacks', function(done) {
  something.on('event', expectEventCallback(done, (payload) => {
    expect(payload).to.have.propertry('foo');
  }));
});

it('handles node callbacks', function(done) {
  doSomething(expectNodeCallback(done, (payload) => {
    expect(payload).to.have.propertry('foo');
  }));
});

Solution 9 - Javascript

I solved it extracting try/catch to a function.

function asyncExpect(test, done){
    try{
        test();
        done();
    } catch(error){
        done(error);
    }
}

Then in it() I call:

it('shall update a host', function (done) {
            testee.insertHost({_id: 'host_id'})
                .then(response => {
                    asyncExpect(() => {
                        expect(response).to.have.property('ok', 1);
                        expect(response).to.have.property('nModified', 1);
                    }, done);
                });

        });

It's also debugable.

Solution 10 - Javascript

Based on this link provided by @richardforrester http://staxmanade.com/2015/11/testing-asyncronous-code-with-mochajs-and-es7-async-await/, describe can use a returned Promise if you omit the done parameter.

Only downside there has to be a Promise there, not any async function (you can wrap it with a Promise, thou). But in this case, code can be extremely reduced.

It takes into account failings from either in the initial funcThatReturnsAPromise function or the expectations:

it('should test Promises', function () { // <= done removed
    return testee.funcThatReturnsAPromise({'name': 'value'}) // <= return added
        .then(response => expect(response).to.have.property('ok', 1));
});

Solution 11 - Javascript

Timers during tests and async sounds pretty rough. There is a way to do this with a promise based approach.

const sendFormResp = async (obj) => {
    const result = await web.chat.postMessage({
        text: 'Hello world!',
    });
   return result
}

This async function uses a Web client (in this case it is Slacks SDK). The SDK takes care of the asynchronous nature of the API call and returns a payload. We can then test the payload within chai by running expect against the object returned in the async promise.

describe("Slack Logic For Working Demo Environment", function (done) {
    it("Should return an object", () => {
        return sdkLogic.sendFormResp(testModels.workingModel).then(res => {
            expect(res).to.be.a("Object");
        })
    })
});

Solution 12 - Javascript

A simpler approach would be using wait-for-expect library.

const waitForExpect = require("wait-for-expect")

test("it waits for the number to change", async () => {
  let numberToChange = 10;

  setTimeout(() => {
    numberToChange = 100;
  }, randomTimeout);

  await waitForExpect(() => {
    expect(numberToChange).toEqual(100);
  });
});

Solution 13 - Javascript

What worked very well for me icm Mocha / Chai was the fakeTimer from Sinon's Library. Just advance the timer in the test where necessary.

var sinon = require('sinon');
clock = sinon.useFakeTimers();
// Do whatever. 
clock.tick( 30000 ); // Advances the JS clock 30 seconds.

Has the added bonus of having the test complete quicker.

Solution 14 - Javascript

You can also use domain module. For example:

var domain = require('domain').create();

domain.run(function()
{
    // place you code here
});

domain.on('error',function(error){
    // do something with error or simply print it
});

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionThomas ParslowView Question on Stackoverflow
Solution 1 - JavascriptJean VincentView Answer on Stackoverflow
Solution 2 - JavascriptRichardForresterView Answer on Stackoverflow
Solution 3 - JavascriptxinthinkView Answer on Stackoverflow
Solution 4 - JavascriptDjebbZView Answer on Stackoverflow
Solution 5 - Javascriptuser3586413View Answer on Stackoverflow
Solution 6 - JavascriptManilView Answer on Stackoverflow
Solution 7 - JavascriptRyan McGearyView Answer on Stackoverflow
Solution 8 - JavascriptSukimaView Answer on Stackoverflow
Solution 9 - JavascriptAmio.ioView Answer on Stackoverflow
Solution 10 - JavascriptPedro R.View Answer on Stackoverflow
Solution 11 - JavascriptJustin RiceView Answer on Stackoverflow
Solution 12 - JavascriptAbhinabaView Answer on Stackoverflow
Solution 13 - JavascriptTinkerTankView Answer on Stackoverflow
Solution 14 - Javascriptabhishek singh baisView Answer on Stackoverflow