I'm facing a problem of exception catching when the context is async
.
Here is my minimal reproducible code
const { EventEmitter } = require('events');
const delaySync = function(ttd) {
return new Promise((resolve) => {
setTimeout(() => resolve(0), ttd);
});
};
const myEvent = new EventEmitter();
myEvent.on('something', async () => {
console.log('Event triggered');
// Just delay for 100ms and then throw
await delaySync(100);
throw new Error('Something happened');
});
describe('Events', () => {
it('should catch error', async () => {
await expect(myEvent.emit('something')).rejects.toThrow('Something happened');
});
});
Basically, I have an event emitter. It has a callback attached on event 'something' which is async. There are some async jobs inside and after 100ms
delay it throws an error.
Normally I'd catch such errors in jest using await expect(fn).rejects.toThrow(...)
however this case is different because expect
fn expects a promise and myEvent.emit('something')
is regular non-promise context.
In this case, I'm getting an error
● Events › should catch error
expect(received).rejects.toThrow()
Matcher error: received value must be a promise or a function returning a promise
Received has type: boolean
Received has value: true
...
(node:20242) UnhandledPromiseRejectionWarning: Error: Something happened
I've tried it with try-catch block as well but it did not work (not able to catch at all);
try {
myEvent.emit('something');
await delaySync(250);
} catch (e) {
expect(e.message).toBe('Something happened');
}
I've tried
// Not throwing because exception is not happening right away
expect(() => myEvent.emit('something')).toThrow('Something happened');
Also this
expect(async () => {
// Will thow after 100ms
myEvent.emit('something');
await delaySync(250);
}).toThrow('Something happened');
But no luck with any of them. 😕
Any ideas on how can I catch the error in this case? or any workaround?
The Eventemitter
is fully sync
and there is no workaround for it. What you can do is to use the eventemitter2 module to replace the original Eventemitter
class. It is fully compatible with Eventemitter
and allows you to use async
actions.
var EventEmitter = require('eventemitter2');
const delaySync = function (ttd) {
return new Promise((resolve) => {
setTimeout(() => resolve(0), ttd);
});
};
const myEvent = new EventEmitter();
myEvent.on('something', async () => {
console.log('Event triggered');
// Just delay for 100ms and then throw
await delaySync(100);
throw new Error('Something happened');
// for async emitters pass promisify true.
}, { promisify: true });
describe('Events', () => {
it('should catch error', async () => {
await expect(myEvent.emit('something')).rejects.toThrow('Something happened');
});
}