Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jest gives no indication that test fails because of unhandled promise rejection. #9210

Open
JoeLangewayClear opened this issue Nov 19, 2019 · 25 comments

Comments

@JoeLangewayClear
Copy link

🐛 Bug Report

When Jest fails a test for what it thinks - wrongly - is an unhandled rejection, it does not indicate this at all.

To Reproduce

This code in a test module causes Jest to wrongly think a promise rejection isn't going to be handled.

describe("whatever", () => {
  it("fails for no reason", async () => {
    const p = Promise.reject(new Error("Shouldn't fail the test"));
    await new Promise(r => setTimeout(r, 100));
    await expect(p).rejects.toMatchObject({
      message: "Shouldn't fail the test",
    });
  });
});

Output:

 FAIL  src/test/plugging-in.test.ts
  DeviceManager - plugging and unplugging
    ○ skipped becomes useful after being plugged in
  whatever
    × fails for no reason (12ms)

  ● whatever › fails for no reason

    an error message

      90 | describe("whatever", () => {
      91 |   it("fails for no reason", async () => {
    > 92 |     const p = Promise.reject(new Error("an error message"));
         |                              ^
      93 |     await new Promise(r => setTimeout(r, 100));
      94 |     await expect(p).rejects.toMatchObject({
      95 |       message: "an error message",

      at Object.it (src/test/plugging-in.test.ts:92:30)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 skipped, 2 total
Snapshots:   0 total
Time:        4.355s, estimated 6s
Ran all test suites matching /plugging-in/i with tests matching "whatever".

Note that nothing in the output conveys the real reason this test failed.

Expected behavior

Something in the output says that Jest failed the test because it can't guarantee that the promise rejection will be handled. For instance, instead of:


     an error message

something like:

    Unhandled Promise Rejection:
    an error message
@JoeLangewayClear
Copy link
Author

JoeLangewayClear commented Nov 19, 2019

A workaround for others encountering this problem, or rather, a work around for the difficulty of Jest not liking unhandled rejections:

/**
 * A function to wrap the Promise constructor such that no rejections are considered unhandled
 * by either Node or Jest. Whatever handlers can still be attached and rejections turned into
 * thrown exceptions where ever the returned promise is awaited.
 */
function makePromise(callback) {
  const promise = new Promise(callback);
  promise.catch(()=>{});
  return promise;
}

@timoxley
Copy link

timoxley commented Jul 7, 2020

Oh wow ok I've just spent far too long trying to figure out how Jest was even seeing the error it was reporting. Really could do with jest saying something, anything, about how it caught the error.

Unhandled error/rejection bugs can be subtle and hard to track down, and often appear when you end up with the worst case async scenario where code is unintentionally executing in the background (e.g. missing return Promise) where it can't be sequenced and errors can't be handled.

Having Jest show the error without indicating anything at all about how it caught the error is very confusing, especially since Jest seems to override/ignore any unhandled rejection/exception handlers you add in test.

@timoxley
Copy link

timoxley commented Jul 7, 2020

@JoeLangewayClear's workaround will suppress these errors, but it's kind of like disabling the engine fault light in your car and claiming you "fixed it"

image

@korobochka
Copy link

I would also like to add that the problem here is not only "no indication", but also the fact that the test fails: I was writing a test for utility function and I need to test that this function handles both successful and failing callbacks passed to it.
Seems like the "negative" test fails for me anyway, even with expect(promise).rejects...., try/catch.

P.S.
Also if I add const promise = Promise.reject('aaaa') to the top level in the file (outside describe) Jest will randomly fail first two tests. Very weird...

@jeysal
Copy link
Contributor

jeysal commented Jul 13, 2020

I would also like to add that the problem here is not only "no indication", but also the fact that the test fails

This is intended, the test should fail on unhandled rejection. Short reasoning here. But nevertheless I agree a hint in the error would be great.

@korobochka
Copy link

That sounds reasonable, but is "unhandled rejection" checked at the end of the test or immediately?
My use case was to have several promises in variables and only do something with them at the end of the test (wait for all and verify some are rejected).

@jeysal
Copy link
Contributor

jeysal commented Jul 13, 2020

Immediately, I believe this is using Node's native handling mechanism for rejected Promises. However note that this does not mean the test case will be aborted, it will be marked as failed but there is no way to stop the test case function execution early.

@timoxley
Copy link

This issue seems related to #5958

@upMKuhn
Copy link

upMKuhn commented Jan 17, 2022

In node v16 the issue has gotten a lot worse.

[Nest] 36135  - 17/01/2022, 10:29:12   DEBUG Auto-corrected AuditLogServiceClient 'baseUrl' value: Removed /api/v1.
[Nest] 36135  - 17/01/2022, 10:29:12   DEBUG Auto-corrected AuditLogServiceClient 'baseUrl' value: Removed /api/v1.
[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 "TypeError: promise.then is not a function".] {
  code: 'ERR_UNHANDLED_REJECTION'
}
end of process....

@sunpietro
Copy link

Is there a way to understand what piece of code is throwing that particular error?
Stack:
NodeJS v16.13.1
jest 26.6.0

@SalahAdDin
Copy link

Getting the same problem today:

❯ node --trace-warnings node_modules/.bin/jest --passWithNoTests --no-cache --runInBand --detectOpenHandles --forceExit --coverage
 PASS  src/application/context.test.tsx
 PASS  src/application/provider.test.tsx
Running coverage on untested files...(node:27184) UnhandledPromiseRejectionWarning
    at emitUnhandledRejectionWarning (internal/process/promises.js:168:15)
    at processPromiseRejections (internal/process/promises.js:247:11)
    at processTicksAndRejections (internal/process/task_queues.js:96:32)
(node:27184) UnhandledPromiseRejectionWarning: Unhandled promise rejection. 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(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:27184) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
    at emitDeprecationWarning (internal/process/promises.js:180:11)
    at processPromiseRejections (internal/process/promises.js:249:13)
    at processTicksAndRejections (internal/process/task_queues.js:96:32)

I'm not using any promise yet.

My setup:

  "devDependencies": {
    "@hookform/resolvers": "2.9.1",
    "@testing-library/dom": "8.13.0",
    "@testing-library/jest-dom": "5.16.4",
    "@testing-library/react": "13.3.0",
    "@testing-library/user-event": "14.2.0",
    "@types/jest": "28.1.1",
    "@types/leaflet": "^1.7.11",
    "@types/node": "17.0.42",
    "@types/react": "18.0.12",
    "@types/react-dom": "18.0.5",
    "@typescript-eslint/eslint-plugin": "5.27.1",
    "@typescript-eslint/parser": "5.27.1",
    "@vitejs/plugin-react": "1.3.2",
    "eslint": "8.17.0",
    "eslint-config-airbnb": "19.0.4",
    "eslint-config-airbnb-typescript": "17.0.0",
    "eslint-config-prettier": "8.5.0",
    "eslint-import-resolver-typescript": "2.7.1",
    "eslint-plugin-import": "2.26.0",
    "eslint-plugin-jest": "26.5.3",
    "eslint-plugin-jsx-a11y": "6.5.1",
    "eslint-plugin-prettier": "4.0.0",
    "eslint-plugin-react": "7.30.0",
    "eslint-plugin-react-hooks": "4.5.0",
    "eslint-plugin-testing-library": "5.5.1",
    "husky": "8.0.1",
    "jest": "28.1.1",
    "jest-environment-jsdom": "28.1.1",
    "jest-watch-typeahead": "1.1.0",
    "lint-staged": "13.0.1",
    "prettier": "2.6.2",
    "ts-jest": "28.0.4",
    "ts-node": "10.8.1",
    "typescript": "4.7.3",
    "vite": "2.9.12",
    "yup": "0.32.11"
  }

Node: v14.19.3

@JaroVDH
Copy link

JaroVDH commented Jul 25, 2022

Looks like it goes wrong in promises & async nesting only?

function _rejecting() {
	return new Promise((resolve, reject) => {
		reject(new Error('An error'));
	});
}

test("this fails", async () => {
	const promise = (function _testMe() {
		return new Promise(async () => {
			await _rejecting();
		});
	})();

	return expect(promise).rejects.toThrowError();
});

test("this doesn't fail", async () => {
	const promise = (async function _testMe() {
		await _rejecting();
	})();

	return expect(promise).rejects.toThrowError();
});

Very minimal reproduction case.
If the rejected promise is nested, it fails the test, if it isn't it won't.
And the promise isn't exactly unhandled, as the async should be chaining them.

The code might look a bit weird like that, but it's sometimes needed to mix async & promises with things like manual timeouts. This is just a minimal repro.

@wmertens
Copy link
Contributor

wmertens commented Aug 16, 2022

Hitting this too. There should be a setting to disable this functionality.

I tried editing jest-circus/build/globalErrorHandlers.js in place, commenting out the inject and restore, and then jest crashes at the site of the uncaught error that later gets caught.

So maybe it's actually a problem with node's detection being too eager?

In any case, the function uncaught could already log the full stack trace of the error, saying it's uncaught.

@wmertens
Copy link
Contributor

When I replace the uncaught function with the below, my tests pass, and my app works just fine. This should be a command line option.

const uncaught = error => {
	console.error(new Error(`uncaught (but ignoring): ${error.stack}`))
	return
	;(0, _state.dispatchSync)({
		error,
		name: 'error',
	})
}

@timoxley
Copy link

When I replace the uncaught function with the below, my tests pass, and my app works just fine.

Sure, though this makes all uncaught rejections into acceptable behaviour, but at least it's giving some indication that the error was unhandled 👍

@wmertens
Copy link
Contributor

Well it's odd, I encounter the same situations in prod and there node doesn't complain about uncaught exceptions or rejections, and I have handlers for them.

So it seems that Jest somehow triggers node into not being ok with async catchers?

@timoxley
Copy link

@wmertens ah that's an issue as well, jest replacing your own handlers

@wmertens
Copy link
Contributor

Well I'm my case that's not an issue, the handlers are only added in prod and they never fire.

@wmertens
Copy link
Contributor

I created a PR that at least marks uncaught errors with their reason, but it doesn't explain why I'm getting uncaught errors that are clearly caught.

@github-actions
Copy link

This issue is stale because it has been open for 1 year with no activity. Remove stale label or comment or this will be closed in 30 days.

@github-actions github-actions bot added the Stale label Aug 20, 2023
@alex-tsx
Copy link

alex-tsx commented Sep 8, 2023

Here is a comment to show, that this issue is still relevant. Very much so!

@github-actions github-actions bot removed the Stale label Sep 8, 2023
@SalahAdDin
Copy link

Here is a comment to show, that this issue is still relevant. Very much so!

👍🏽

@ctsstc
Copy link

ctsstc commented Dec 7, 2023

Given it's been 4 years since the initial opening of this ticket it may be good to create a reproduction of this in the current state-of-things, in terms of last node LTS and Jest version.

@JaroVDH
Copy link

JaroVDH commented Dec 7, 2023

I've tested the earlier mentioned reproduction case and it seems to still be an issue in Node v20.10.0, Jest v29.3.1.

@MireilleMedhat
Copy link

Any updates about this bug? this should be critical, it's affecting the coverage in our project (and I believe everybody's) and there is no way to work around it except to just not test it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.