Troubleshooting component that relies on an event emitter using Promises with Mocha+Chai

I’m testing a component that relies on an event emitter using Promises with Mocha+Chai. Here’s my current test code:

On the console, I’m encountering an UnhandledPromiseRejectionWarning even though the reject function is being called. The warning message appears immediately with AssertionError: Promise error, and then, after about 2 seconds, I get:

Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test. The issue is puzzling because the catch callback is executed (suggesting that reject is being called), but it seems like the assert.isNotOk(error…) is causing problems. If I comment out assert.isNotOk(error…), the test runs without warning and completes as expected.

Can someone help me understand why this UnhandledPromiseRejectionWarning is occurring and why commenting out the assertion affects the test execution?

The issue is caused by the following code:

.catch((error) => {
assert.isNotOk(error, 'Promise error');
done();
});

If the assertion fails, it throws an error. This error prevents done() from being called because the code errors out before reaching that point, leading to the test timeout.

The UnhandledPromiseRejectionWarning is also triggered by the failed assertion. When an error is thrown inside a catch() handler without a subsequent catch() handler, the error can be swallowed, which is highlighted by the warning.

To handle promise-based code testing in Mocha more effectively, you should avoid using done() and instead return a promise directly from your test. Mocha will handle any errors and manage the test completion automatically.

Here’s how you can rewrite the test:

it('should transition with the correct event', () => {
return new Promise((resolve, reject) => {
// Setup and emit events
}).then((state) => {
assert(state.action === 'DONE', 'should change state');
}).catch((error) => {
assert.isNotOk(error, 'Promise error');
});
});

For those encountering the UnhandledPromiseRejectionWarning outside of a testing environment, it is likely because the code does not handle the eventual error in the promise. For example, the following code will trigger the warning:

new Promise((resolve, reject) => {
return reject('Error reason!');
});

This produces: (node:XXXX) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Error reason!

To resolve this warning, ensure that you handle the error by adding a .catch() or using the second parameter in the .then() function:

new Promise((resolve, reject) => {
return reject('Error reason!');
}).catch(() => { /* handle error here */ });
Or:
new Promise((resolve, reject) => {
return reject('Error reason!');
}).then(null, () => { /* handle error here */ });

By including proper error handling, you can prevent the UnhandledPromiseRejectionWarning and manage errors effectively.

I encountered an error while stubbing with Sinon. The solution is to use the sinon-as-promised npm package for handling promises with stubs. Instead of:

sinon.stub(Database, 'connect').returns(Promise.reject(Error('oops')));

You should use:

require('sinon-as-promised');
sinon.stub(Database, 'connect').rejects(Error('oops'));

The sinon-as-promised package provides the rejects method for rejecting promises, and there’s also a resolves method (note the ‘s’ at the end) for resolving promises.