Do you want to request a feature or report a bug?
feature
What is the current behavior?
When writing unit tests that involve promises, if a developer does not return the promise (or use async
/await
), uncaught rejections bubble up to Node's default unhandledRejection
handler. For example:
That's fine, except that when running tests locally, because of the way Jest aggressively clears the console as concurrent test suites finish running, Node's default unhandledRejection
logging sometimes gets cleared off the terminal. I've found this to be especially problematic on machines with more CPUs where many tests may be running concurrently.
That can make spotting these errors trickier in development. The default node error handler always prints the message in CI, when Jest isn't clearing the console at all, but in dev it's inconsistent.
If the current behavior is a bug, please provide the steps to reproduce and either a repl.it demo through https://repl.it/languages/jest or a minimal repository on GitHub that we can yarn install
and yarn test
.
What is the expected behavior?
It would be great if Jest had a default unhandledRejection
handler baked in that provided the same error message and stack trace formatting we get for other errors. And, more importantly, if the default handler could ensure the error would not get wiped away by any console clearing Jest does in dev.
As a quick fix, I added an unhandledRejection
handler in my Jest setup that logs the error to the console.
But if you guys think this could be useful to other people, I'd be happy to take a stab at implementing a more robust solution in Jest itself!
Please provide your exact Jest configuration and mention your Jest, node, yarn/npm version and operating system.
Jest v19.0.2
Node v6.9.4
yarn v0.21.3
Mac OS X 10.12.3
Hm, something like this might be handy.
cc: @dmitriiabramov @rogeliog
Yeah, this is a really good point... Even if Jest was not clearing the console, I think that the default UnhandledPromiseRejectionWarning
is not super intuitive and makes it a bit hard to figure out the error
yea! I remember we already thought about it in the past, but couldn't implement it because of Jasmine dependency. Now that we started migrating away from jasmine i think it'd be pretty easy to implement!
@dmitriiabramov when you guys thought about this before, did you have anything specific in mind?
i think the easiest way to implement something like this is to have a global handler and save all unhandled errors to a test state.
then when all tests are done, check the state, if there's any errors, fail the test and log them
@dmitriiabramov would it be possible to save the stack trace for the errors as well? if you're messy like I am and coming back into some code after time away and you're not certain where your tests are timing out at, it just takes longer. Thanks in advance!
I'm on vacation for the next few weeks, but I'd be happy to have a go at this when I'm back!
A small workaround I've figured out. Use setupFiles
configuration option and create a simple file like this. If you need it for a browser, just check out this..
process.on('unhandledRejection', (reason) => {
console.log('REJECTION', reason)
})
Then suddenly all warnings are replaced by nice stack trace :)
Edit: One caveat is this warning instead. I am not sure what happens with a really large project. (node:8452) Warning: Possible EventEmitter memory leak detected. 11 unhandledRejection listeners added. Use emitter.setMaxListeners() to increase limit
@FredyC here's how I avoid the memory leak:
// In Node v7 unhandled promise rejections will terminate the process
if (!process.env.LISTENING_TO_UNHANDLED_REJECTION) {
process.on('unhandledRejection', reason => {
throw reason
})
// Avoid memory leak by adding too many listeners
process.env.LISTENING_TO_UNHANDLED_REJECTION = true
}
In this snippet I don't just log the error but I throw it to make sure jest test
will return a non-zero exit code. This is very useful in a ci context.
@stipsan That's kinda surprising that setting env variable here actually makes it shared between other jest child processes. Not sure how that works really, but good to know :)
This seems to have just gotten a lot more important with Jest 20. I'm seeing expectations that are after a.resolves
like .resolves.isInstanceOf()
just print this UnhandledPromiseRejectionWarning
and don't even register with the jest final output that there was a test failure.
@outdooricon yeah, it's very easy to miss. And in some cases I've seen that the child process the test is running in is killed before Node have the chance to log it so it's not in any output at all.
I'm not sure if it's related, but I'll share my own experience here... I've found the warning appears inconsistently between Jest test runs. Sometimes it occurs in --watch
mode, sometimes it happens on a full test run once and then not again the second time. I'm completely befuddled by the inconsistency. Anyone else ever encountering similar issues?
@thomashuston Any update on this?
This seems to have been implemented in Jest 21, and it is extremely annoying. Any asynchronously handled promises that is rejected before having a handler attached makes tests fail. And I don't find any way to deactive this behaviour.
Any idea of how to overcome this? Thanks.
Yeah this is kind of a blocker for me. Here's a small test to demonstrate the issue:
test('this should work', (done) => {
const n = Promise.reject('nope');
setTimeout(() => {
n.catch((err) => console.log('Caught: ', err));
});
setTimeout(() => {
// Never makes it to this point
console.log('here');
done();
}, 10);
});
This test fails in Jest 21.x, but runs fine in Jest 20.x. If I were to catch the exception synchronously (NOT inside a setTimeout), the test passes in Jest 21.x as expected.
This looks like a contrived exception, but in my actual code, I'm having a Promise reject inside of an Express router that I'm testing. I'm testing the router using Supertest, so I have to use done()
after running assertions on the http response.
@MikeyBurkman I have just faced with the same issue, did you figure out how to deal with it?
@ramusus Unfortunately, the best I could do was just downgrade to 20.x. I haven't looked into updating back to 21.x since Oct though, so maybe things have changed.
As noted, the original issue has been handled (in #4016), so closing. But as mentioned above, it means that you cannot attach catch
asynchronously. @MikeyBurkman could you open up a separate issue for it?
(Also see #5311)
Hey, for posterity we've made the errors much nicer in Node 10
@benjamingr that's awesome! Mind linking to the pr (yes, I know I'm being lazy...)
I found just logging the errors from my setup file wasn't working, this did tho::
import * as fs from 'fs';
let errCounter = 0;
process.on('unhandledRejection', (reason) => {
fs.writeFileSync(`error${errCounter++}.txt`, reason.stack);
});
Most helpful comment
@FredyC here's how I avoid the memory leak:
In this snippet I don't just log the error but I throw it to make sure
jest test
will return a non-zero exit code. This is very useful in a ci context.