Jest: Test fails when mock function returns rejected Promise

Created on 1 Apr 2019  路  5Comments  路  Source: facebook/jest

馃悰 Bug Report

The following test fails because a rejected Promise is set as return value:

test("My test", async () => {
  const myMockFunc = jest.fn().mockReturnValueOnce(Promise.reject("error"))

  setTimeout(() => myMockFunc(), 100)
  await new Promise(resolve => setTimeout(resolve, 200))

  expect(myMockFunc.mock.calls.length).toBe(1)
})

Expected behavior

The test should pass. If e.g. a resolved Promise is returned it passes.

How to reproduce

Execute the test above.

Run npx envinfo --preset jest

System:
    OS: macOS 10.14.3
    CPU: (8) x64 Intel(R) Core(TM) i7-4870HQ CPU @ 2.50GHz
  Binaries:
    Node: 11.12.0 - /usr/local/bin/node
    npm: 6.7.0 - /usr/local/bin/npm
  npmPackages:
    jest: ^24.5.0 => 24.5.0 
Bug Report Needs Repro Needs Triage

All 5 comments

Because using Promise.reject() will create a rejected promise immediately, a rejected promise without catch will throw an error, so the test fails.

You can return rejected promise like below.
It create a rejected promise when you call this function, instead of in the declaration.

jest.fn(() => Promise.reject('error'));
//or
jest.fn().mockImplementationOnce(() => Promise.reject('error'));

The test will look like:

test('My test', async () => {
  const myMockFunc = jest.fn(() => Promise.reject('error'));

  setTimeout(
    () =>
      myMockFunc().catch(e => {
        console.log(e); // error
      }),
    100
  );
  await new Promise(resolve => setTimeout(resolve, 200));

  expect(myMockFunc.mock.calls.length).toBe(1);
});

This is intended behavior, if an unhandled rejection occurs while your tests are running, they will fail. You should handle rejections as @WeiAnAn described.
The minimal repro for this is something like

test("t", async () => {
  Promise.reject();
  await new Promise(resolve => setTimeout(resolve, 0));
});

Thank you for your clarification. That cleared things up for me a lot. Thank you for this amazing test framework!

This is intended behavior, if an unhandled rejection occurs while your tests are running, they will fail.

How can the test runner know that the promise is unhandled before the test has finished running? It is perfectly valid to handle a rejected promise _after_ is has rejected, in which case it is no longer unhandled, and Jest should let you do that.

test("t", (done) => {
  var p = Promise.reject();
  setTimeout(() => {
    p.catch(done)
  }, 0);
});

The workaround of course is to catch your promise and turn it into into some sort of result type, then unwrap it again just before you attach your catch handler, but this seems like unnecessary gymnastics to go through just to appease the test framework.

@juggernaut0 No, it is not perfectly valid. Running node on

const p = Promise.reject();
setTimeout(() => {
  p.catch(() => {});
}, 0);

results in

(node:23000) UnhandledPromiseRejectionWarning: undefined
(node:23000) 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:23000) [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.
(node:23000) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)

Code that causes Node warnings / relies on deprecated Node behavior rightly causes tests to fail.

Was this page helpful?
0 / 5 - 0 ratings