Unit testing promises

How to test promise-returning code.

Unit testing promises in JavaScript does not have to be painful. Here is how to solve common pain points.

Too much boilerplate

Most of the boilerplate when testing promise-returning code comes from controlling the test runner. For example, when using qunit one needs to stop the test (or declare the test as async), then start the runner again:

1
2
3
4
5
6
7
8
QUnit.start('promise test', function () {
QUnit.stop();
foo().then(function (value) {
QUnit.equal(value, 'foo');
}).finally(function () {
QUnit.start();
});
});

Notice that we have more lines stopping and starting QUnit test runner than actually testing the resolved value.

Resolved vs rejected promise

You should check if the promise that should be resolved is not rejected instead, again adding to the boilerplate

1
2
3
4
5
6
7
8
9
10
QUnit.start('promise test', function () {
QUnit.stop();
foo().then(function (value) {
QUnit.equal(value, 'foo');
}, function () {
QUnit.fail('promise rejected!');
}).finally(function () {
QUnit.start();
});
});

Catch errors

The promises catch thrown errors hoping that somewhere there would be an error handler ready to handle them. If you do not have an error handler and do not close the chain using .done any caught error silently disappears. Thus you should always at least close the promise chain, adding to the boilerplate

1
2
3
4
5
6
7
8
9
10
QUnit.start('promise test', function () {
QUnit.stop();
foo().then(function (value) {
QUnit.equal(value, 'foo');
}, function () {
QUnit.fail('promise rejected!');
}).finally(function () {
QUnit.start();
}).done();
});

Solution 1: use plugin

Shameless self-promotion: I wrote a qunit-promises plugin to remove the boilerplate to stop / start the qunit runner and check resolved vs rejected expectations. Same example as above looks much simpler.

1
2
3
QUnit.test('promise test', function (assert) {
assert.willEqual(foo(), 'foo', 'resolves with value "foo"');
});

For Jasmine fans, there is jasmine-as-promised.

Solution 2: use promise-supporting test runner

If you use behavior-drive development (BDD), you can use mocha testing framework which has built-in promise support. Any unit test that returns a promise is automatically processed as async, including error checking or rejected promise checking. To check the resolved value, just attach the assertions to the original promise chain. In the example below I am using chai-as-promised assertion library.

1
2
3
it('promise test', function(){
return foo().should.eventually.equal('foo');
});

I prefer using expect.js assertion library instead for clarity

1
2
3
4
5
it('promise test', function(){
return foo().then(function (value) {
expect(value).to.equal('foo');
});
});

Another interesting test runner with native promises support is vows.js.