jest: Do not filter out stack trace for unhandled rejections
š Bug Report
In strict unhandled rejections mode (https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode), the stack trace of unhandled rejection is not displayed in the console. This mode is default in Node.js 15.
To Reproduce
Steps to reproduce the behavior:
- Run a test with an unhandled rejection, for example:
test("foo", () => {
Promise.reject(new Error());
expect(1).toBe(1);
});
- The output is:
RUNS ./index.test.js
node:internal/process/promises:218
triggerUncaughtException(err, true /* fromPromise */);
^
[UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "Error".] {
code: 'ERR_UNHANDLED_REJECTION'
}
Expected behavior
The stack trace is displayed in the console.
Link to repl or repo (highly encouraged)
https://github.com/vkrol/jest-unhandled-rejection-stack-trace
envinfo
System:
OS: Windows 10 10.0.19042
CPU: (8) x64 Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz
Binaries:
Node: 15.1.0 - ~\scoop\apps\nodejs\current\node.EXE
Yarn: 1.22.5 - ~\scoop\apps\yarn\current\Yarn\bin\yarn.CMD
npm: 7.0.8 - ~\scoop\apps\nodejs\current\npm.CMD
npmPackages:
jest: 26.6.3 => 26.6.3
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 52
- Comments: 32 (10 by maintainers)
Currently, you can just use
NODE_OPTIONS=--unhandled-rejections=warn yarn test
to resolve this problem.This is such a frustrating default behavior. Usually I can quickly figure out the location of the error, but today Iāve been working on the wrong area of my app.
Turns out the bug wasnāt caused by my code changes, but an upgraded dependency in unrelated codeā¦
I had to use VS Code debugger to trap the error. It was pretty frustrating to find all the line number details Iād expect were there. They were simply dropped by node+jestās output.
Is there a way to let users know the risk of using
NODE_OPTIONS="--unhandled-rejections=strict"
?Or is there a node.js option to make the crash behavior less user-hostile?
+1 Happened to me with the node version 15 I downgraded the node to
14.15.4
, the issue is resolved--unhandled-rejections=warn
simply ignores the rejection, how does this resolves the problem of a missing stack trace?I did a little more digging - per the Node source code, it looks like Node should be printing a proper stack trace for uncaught exceptions, as long as the rejection reason is an instance of
Error
:https://github.com/nodejs/node/blob/09c9e5dea42fd11606f0d8988462e291cbfafd32/lib/internal/process/promises.js#L244
However, for some reason, it seems like errors that are thrown in Jest code arenāt actually errors. You can reproduce this easily with the node interpreter. Compare the output of the following scripts:
The former (which is an actual error) includes a very nice and easy to understand stack trace; the latter (which is not an error) shows the reference to Node internals that the original report in this thread shows. Iām not sure why an error isnāt seen as
instanceof Error
by Node - it could be the fault of Jest, or some unknown polyfill, or something else. But this seems to be the root of why we donāt get nice stack traces in Jest.Interestingly, if you add the following to a script referenced from
setupFiles
, Node does show a nice stack trace - presumably the defaultuncaughtException
handler is behaving better than the defaultunhandledRejection
code.@vicary it should warn about them, but still keep running. If you want it to warn, and then fail your test suite with a clear error that you can more easily track down, use the
warn-with-error-code
mode (see the https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode link from the original comment).For those using jest through npm scripts, that means using something like this:
Without the unhandled rejection flag, youād see this:
But using
warn-with-error-code
gets us this, which is far more useful:@LvChengbin
NODE_OPTIONS=--unhandled-rejections=warn npm test
saved me!The PR landed yesterday and will go out with the next v12/v14/v16/v17 versions.
Probably a good idea to update the docs regardless.
Hey, saw this in the Node repo
Pull request based on @ItamarGronich 's issue in Node: https://github.com/nodejs/node/pull/41682
We likely wonāt fix the issue of primitives throwing (also raised there, at least Iām personally -1 but other members may feel differently) but Iāve altered the check for unhandled rejection error logging to check if the error has a
.stack
property rather thaninstanceof Error
to deal with cross realm errors.As mentioned previously you can use the
'unhandledRejection'
event (on process) to work around it in the meantime. When I designed the hook it was meant to be versatile and enable this sort of use case, you would set it up in globalSetup for example you may choose to:We found a solution, although I am sure it has a performance impact.
They do attach it. But if you look carefully they restore it after the tests are complete. So what is happening is the tests are completing, they are restoring the (non-existant) event handlers, and then node is blowing up. Since the promise is rejecting after the test suite finishes.
We found a workaround, by flushing the promises after each test, the error is forced to happen exactly where the test fails, and it even marks the test as failed. I am sure it will have a performance impact, so I am testing that a bit.
https://www.npmjs.com/package/flush-promises
Oops, somebody close it then
I think Iāve narrowed this down to an issue with Jestās
jest-environment-node
(possibly other environments as well). I added the following log below this line in the Node environment implementation:When I run Jest, I see the output
Prototypes equal? false
- which explains why Node doesnāt think the promise rejection reason isnāt actually an Error and thus doesnāt print the error with its stack trace.As for a fixā¦ letās consider the following simple test case:
If we run that with the latest published version of Jest, weāll see the
[UnhandledPromiseRejection ...
error without a stack trace.Now, letās add the following line below this line in the Node environment implementation:
Now when we run the above test, we see the actual stack trace:
This isnāt perfect - for instance, it doesnāt handle someone rejecting a promise with a
TypeError
or any other class that inherits fromError
. If we change the above simple test case toPromise.reject(new TypeError('type error in test'))
, weāre back to the same uninformative error as before:Copying over the
TypeError
prototype to the newglobal
object fixes this too, but this feels like playing whack-a-mole. I donāt understand Jest environments to know if this is the right fix, or if thereās something else that would work better instead.FWIW, it looks like thereās precedence for this kind of change. For instance, https://github.com/facebook/jest/pull/7626 copied over the
ArrayBuffer
prototype to the environment so thatBuffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]) instanceof ArrayBuffer
would be truthy.@SimenB does this seem like the right fix? Iād be happy to make a PR if so.
Yep! Also just did some before/after testing on our admittedly small test base of ~600 tests and 5 snapshot tests. Performance difference was pretty minimal. Ultimately itās just an empty promise that resolves so I canāt imagine it will have too much overhead.
@timritzer ah, nice find! That matches up with the fact that the following code will produce an error handled by Jest when
await new Promise(...)
is there, but will produce an unhandled promise rejection when itās missing:Iām using handling it with:
in jest.setup.js
@SimenB
No, this flag doesnāt help. What kind of flag is that? I canāt find any mention of it anywhere?