Asynchronous Unit Tests

Arnaud Dostes
Mar 23 · 2 min read

When unit testing asynchronous code, the unit tests must return either return a promise that resolve or call the callback function (done).

If neither these things are done, then the test will fail (timeout).
If both are done, the test will fail (over specified) with mocha, but pass with jest.

Let’s see what this means, using jest and mocha as test runners, and let’s establish samples that will pass with either runner.

Here’s a small asynchronous function to be tested:

const doSomethingAsync = () => {
const p = new Promise(function(resolve, reject) {
setTimeout(() => {
resolve('OK');
}, 300);
});
return p;
};

If the unit test does not return a promise, or if the unit does not call the done function, mocha will throw a bunch of warnings, but more significantly jest will mark the test as successful even when the assertion fails. I am not posting code to exemplify that, as to not mislead inattentive readers.

Writing a unit test using the done callback

describe('when using the done function', () => {
it('should pass', done => {
doSomethingAsync()
.then(msg => {
expect(msg).toEqual('OK');
})
.then(done, done);
});
});

This might look odd at first, we could have called done() right after the expect. The problem is when the assertion fails: while it would have worked fine with jest; with mocha the done function would have never been called as code after a failed assertion is not executed. This would have resulted in a timeout on top of a failing test which can become frustrating when debugging, or can clog a CI system.

Using the then(done,done) block guarantees that the done function is called whether the promise resolves, rejects or if the assertion fails, avoids nasty error messages and lengthy, frustrating timeouts.

The second option is to simply return a promise that resolves

Writing a unit test that returns a Promise

describe('when returning a Promise', () => {
it('should pass', () => {
return doSomethingAsync().then(msg => {
expect(msg).toEqual('OK');
});
});
});

This is probably the simplest way, returning the promise guarantees the assertion is executed and there is no concern about the done function being called in the right place. This works well with both jest and mocha.

Finally, there is a useful linting rule I recommend, no-return-and-callback, that is part of the eslint-plugin-mocha package.

Arnaud Dostes

Written by

Previously Paris and Geneva, currently New York. Mostly JavaScript, Java in a past life, and Python in a future one. Building teams that build applications.