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;
d
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 );
}
}
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:
function expectEventCallback(done, fn) {
return function() {
try { fn(...arguments); }
catch(error) { return done(error); }
done();
};
}
function expectNodeCallback(done, fn) {
return function(err, ...args) {
if (err) { return done(err); }
try { fn(...args); }
catch(error) { return done(error); }
done();
};
}
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');
}));
});
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");
})
})
});
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.
I asked the same thing in the Mocha mailing list. They basically told me this : to write asynchronous test with Mocha and Chai :
if (err) done(err);
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.
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