简体   繁体   中英

How to test status of a promise inside Promise.finally() without awaiting it in production code

I am using Promise.prototype.finally() (or try-catch-finally in an async function) in my production code to execute some follow-up code without changing resolution/rejection status of the current promise.

However, in my Jest tests, I would like to detect that the Promise inside finally block wasn't rejected .

edit : But I don't want to actually await the Promise in my "production" code (there I care only about errors re-thrown from catch, but not about errors from finally).

How can I test for that? Or at least how to mock the Promise.prototype to reject the current promise on exceptions from finally?

Eg if I would be testing redux action creators, the tests pass even though there is a message about an unhandled Promise rejection:

https://codesandbox.io/s/reverent-dijkstra-nbcno?file=/src/index.test.js

test("finally", async () => {
  const actions = await dispatchMock(add("forgottenParent", { a: 1 }));
  const newState = actions.reduce(reducer, undefined);
  expect(newState).toEqual({});
});

const dispatchMock = async thunk => {...};

// ----- simplified "production" code -----

const reducer = (state = {}, action) => state;
const add = parentId => async dispatch => {
  dispatch("add start");
  try {
    await someFetch("someData");
    dispatch("add success");
  } catch (e) {
    dispatch("add failed");
    throw e;
  } finally {
    dispatch(get(parentId)); // tests pass if the promise here is rejected
  }
};
const get = id => async dispatch => {
  dispatch("get start");
  try {
    await someFetch(id);
    dispatch("get success");
  } catch (e) {
    dispatch("get failed");
    throw e;
  }
};
const someFetch = async id => {
  if (id === "forgottenParent") {
    throw new Error("imagine I forgot to mock this request");
  }
  Promise.resolve(id);
};
dispatch(get(parentId)); // tests pass if an exception is thrown here

There is no exception throw in that line. get(parentId) might return a rejected promise (or a pending promise that will get rejected later), but that's not an exception and won't affect control flow.

You might be looking for

const add = parentId => async dispatch => {
  dispatch("add start");
  try {
    await someFetch("someData");
    dispatch("add success");
  } catch (e) {
    dispatch("add failed");
    throw e;
  } finally {
    await dispatch(get(parentId));
//  ^^^^^
  }
};

Notice that throwing exceptions from a finally block is not exactly a best practice though.

edit : more general solutions available on https://stackoverflow.com/a/58634792/1176601


It is possible to store the Promise in a variable accessible in some helper function that is used only for the tests, eg:

export const _getPromiseFromFinallyInTests = () => _promiseFromFinally
let _promiseFromFinally

const add = parentId => async dispatch => {
  ...
  } finally {
    // not awaited here because I don't want to change the current Promise
    _promiseFromFinally = dispatch(get(parentId));
  }
};

and update the test to await the test-only Promise:

test("finally", async () => {
  ...
  // but I want to fail the test if the Promise from finally is rejected
  await _getPromiseFromFinallyInTests()
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM