Тестирование асинхронной функции, используемой в качестве обратного вызова из setTimeout - PullRequest
0 голосов
/ 15 февраля 2019

Я пытаюсь написать тест для простой функции опроса, которая проверяет конечную точку API, пока не получит ответ 200 OK и не попытается повторить запрос 400 или 500 вплоть до maxAttempts.У меня проблемы с модульными тестами, потому что .then() и .catch(), кажется, никогда не выполняются независимо от ложного ответа.

Функция, которую я пытаюсь проверить.

const waitForSsl = ({
  onSuccess,
  onFailure,
  interval = 3,
  maxAttempts = 10,
}) => {
  const pingInterval = interval * 1000; // in seconds
  let attempts = 0;

  // TODO Add CertController to Laravel API to support this call.
  const ping = () => Axios.get('/status')
    .then((res) => { return onSuccess(); })
    .catch(() => {
      if (attempts < maxAttempts) {
        attempts += 1;
        setTimeout(ping, pingInterval);
      } else {
        onFailure();
      }
    });

  // Give server a chance to restart by waiting 5 seconds before starting pings.
  setTimeout(ping, 5000);
};

Я могу проверить, что функция выполняет именно то, что я ожидаю в дикой природе, но я хотел бы провести модульный тест для моего собственного спокойствия.

Это моя первая попытка с использованием jest и sinon

  it('Should only poll maxAttempts + 1 times', () => {
    jest.useFakeTimers();
    const onSuccessCallback = () => 'success!';
    const onFailureCallback = () => 'failed';
    const getStub = sinon.stub(Axios, 'get');
    getStub.rejects();

    ssl.waitForSsl({
      onSuccess: onSuccessCallback,
      onFailure: onFailureCallback,
      maxAttempts: 1,
    });

    expect(setTimeout).toHaveBeenCalledTimes(2);
  });

Этот тест не пройден с ошибкой Expected mock function to have been called two times, but it was called one time

Я нашел post , но проект еще не использует async / await (ES8) и просто вызов Promise.resolve () без await не решает проблему.

Я открыт для использования, moxios или jest.mock() но я чувствую, что нет способа проверить разрешенное / отклоненное обещание, когда оно используется в качестве обратного вызова в setTimeout.Испытание работающего модуля и объяснение того, как работает эта насмешка, было бы идеальным ответом.

1 Ответ

0 голосов
/ 16 февраля 2019

Это хороший вопрос, потому что он привлекает внимание к некоторым уникальным характеристикам JavaScript и тому, как он работает под капотом.Для полной разбивки при тестировании async кода при использовании Timer Mocks см. Мой ответ здесь .


По этому вопросу важно отметить, что Таймеры Mock заменяют функции типа setTimeout на mocks , которые помнят, как они были вызваны.Затем, когда вызывается jest.advanceTimersByTime (или jest.runTimersToTime для Jest <22.0.0), <code>Jest запускает все, что было бы выполнено за прошедшее время.

Обратите внимание, чтоsetTimeout обычно планирует сообщение для очереди сообщений JavaScript, но Timer Mocks меняет это так, чтобы все выполнялось в текущем выполняемом сообщении .


С другой стороны, когда Promise разрешает или отклоняет, обратный вызов назначается в очередь Promise Jobs , которая запускается после завершения текущего сообщения и до начала следующего сообщения .

Так что любой текущийвыполнение синхронного кода завершится до того, как любой из обратных вызовов Promise сможет запустить.


Так что в этом случае вам нужно позвонить jest.advanceTimersByTime (или jest.runTimersToTime для Jest <22.0.0) для выполнения вызова <code>ping, запланированного с setTimeout.

Сложность в том, что функция ping ставит в очередь обратный вызов в очереди заданий Promise, которая не будет выполняться до текущего синхронного сообщениязавершает.

Так йоЗатем необходимо прервать текущее синхронное сообщение, чтобы разрешить обратный вызов в очереди заданий Promise.Это проще всего сделать, сделав вашу тестовую функцию async и вызвав await в разрешенном Promise, который, по сути, ставит в очередь оставшуюся часть теста в конце очереди заданий Promise, позволяя выполнить все до того, как она будет запущена первой.


Итак, чтобы свести все воедино, вашему тесту нужно чередовать, увеличивая время и позволяя обратным вызовам Promise запускаться так:

it('Should only poll maxAttempts + 1 times', async () => {  // use an async test function
  jest.useFakeTimers();
  const onSuccessCallback = () => 'success!';
  const onFailureCallback = () => 'failed';
  const getStub = sinon.stub(Axios, 'get');
  getStub.rejects();

  const maxAttempts = 1;
  ssl.waitForSsl({
    onSuccess: onSuccessCallback,
    onFailure: onFailureCallback,
    maxAttempts
  });

  for (let i = 0; i < maxAttempts; i++) {
    jest.advanceTimersByTime(5000);  // advance the time
    await Promise.resolve();  // allow queued Promise callbacks to run
  }
  expect(setTimeout).toHaveBeenCalledTimes(2);  // SUCCESS
});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...