Как проверить статус обещания внутри Promise.finally (), не ожидая его в производственном коде

Я использую Promise.prototype.finally () (или try-catch-finally в функции async) в моем производственном коде, чтобы выполнить некоторый дополнительный код без изменения статуса разрешения / отклонения текущего обещания.

Однако в своих тестах Jest я хотел бы обнаружить, что промис внутри блока finally не был отклонен.

edit: Но я не хочу на самом деле ждать Promise в моем производственном коде (там меня волнуют только ошибки, повторно генерируемые из catch, но не ошибки из finally).

Как я могу это проверить? Или, по крайней мере, как издеваться над Promise.prototype, чтобы отклонить текущее обещание об исключениях из finally?

Например. если бы я тестировал redux создателей действий, тесты проходят, даже если есть сообщение о необработанном отклонении обещания:

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);
};

person Aprillion    schedule 06.08.2020    source источник
comment
Вы говорите, что исключение создается внутри get или внутри dispatch?   -  person Dai    schedule 06.08.2020
comment
Если исключение выбрасывается внутри finally как в нормальном (синхронном), так и в _2 _ / _ 3_ (не при использовании цепочки методов Promise), исключение следует повторно выбросить на верхнем уровне await, если оно не было проглочено где то посередине. Если вы видите, что исключения проглатываются незаметно, я предлагаю использовать параметр «Прервать все выброшенные исключения» (или параметр с аналогичным названием) в вашем отладчике JavaScript. По умолчанию большинство отладчиков JS прерывают работу только для _необработанных_ / неперехваченных исключений. Надеюсь, таким образом вы сможете увидеть, где его поймают и проглотят.   -  person Dai    schedule 06.08.2020
comment
@ Дай, throw e снова бросил его внутрь блока catch get. и нет, нет, он работает в соответствии со спецификацией, исключения изнутри finally игнорируются в том, что касается разрешенного статуса внешнего обещания = ›он проглатывается самим finally, и это правильное поведение (но я хочу изменить это поведение внутри моих тестов) - см. вкладку тестов и вложенную вкладку Проблемы при выполнении на связанных кодах и   -  person Aprillion    schedule 06.08.2020
comment
для ясности, исключение повторно выбрасывается на верхнем уровне. но он не отклоняет обещание и не приводит к сбою асинхронного теста   -  person Aprillion    schedule 06.08.2020


Ответы (2)


dispatch(get(parentId)); // tests pass if an exception is thrown here

В этой строке нет исключения. get(parentId) может вернуть отклоненное обещание (или ожидающее обещание, которое будет отклонено позже), но это не исключение и не повлияет на поток управления.

Вы можете искать

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));
//  ^^^^^
  }
};

Обратите внимание, что создание исключений из finally блока не совсем лучшая практика.

person Bergi    schedule 06.08.2020
comment
ожидание отклоненного обещания в finally не изменяет уже разрешенное внешнее обещание ... Я знаю, что это не лучшая практика, поэтому я хочу проверить, что этого НЕ происходит = ›Мне нужно, чтобы тест не прошел, если это произойдет - person Aprillion; 06.08.2020
comment
@Aprillion Я не могу воспроизвести. async function example() { try { throw new Error("inner"); } finally { await Promise.reject(new Error("outer")); } } обязательно отклонит внешнюю ошибку и проигнорирует внутреннюю. - person Bergi; 06.08.2020
comment
не бросайте внутрь try плз - async function example() { try { return 1 } finally { await Promise.reject(new Error("outer")); } } - person Aprillion; 06.08.2020
comment
Я хочу, наконец, заявить об Обещании ... - person Aprillion; 06.08.2020
comment
@Aprillion Неважно, что делает блок try - он всегда выдает ошибку outer из finally и отклоняет example(), даже если вы return 1; - person Bergi; 06.08.2020
comment
хм, может Jest делает что-то нестандартное .. тесты в codeandbox у меня сейчас не работают - я вернусь, если мне удастся обновить свой пример - person Aprillion; 06.08.2020
comment
хорошо, протестировано локально, и вы были правы - я наконец пропустил await внутри - он должен быть вне dispatch, чтобы мои тесты работали, потому что мой dispatchMock асинхронный. Благодарность! - person Aprillion; 06.08.2020
comment
черт возьми ... ожидание внутри наконец меняет бизнес-логику: ((Я не хочу отказываться от обещания в производственном коде - только в тестовом коде. - person Aprillion; 06.08.2020
comment
Я нашел решение (опубликовано как ответ), спасибо, что указали мне правильное направление. - person Aprillion; 06.08.2020

edit: более общие решения доступны на https://stackoverflow.com/a/58634792/1176601


Можно сохранить обещание в переменной, доступной в некоторой вспомогательной функции, которая используется только для тестов, например:

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));
  }
};

и обновите тест, чтобы дождаться только тестового обещания:

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