Тестирование функции рекурсивного опроса с использованием шутников и фальшивых таймеров - PullRequest
0 голосов
/ 20 сентября 2018

Я создал службу опроса, которая рекурсивно вызывает API, и при успешном выполнении API, если выполняются определенные условия, снова продолжает опрос.

/**
   * start a timer with the interval specified by the user || default interval
   * we are using setTimeout and not setinterval because a slow back end server might take more time than our interval time and that would lead to
   * a queue of ajax requests with no response at all.
   * -----------------------------------------
   * This function would call the api first time and only on the success response of the api we would poll again after the interval
   */
  runPolling() {
    const { url, onSuccess, onFailure, interval } = this.config;
    const _this = this;
    this.poll = setTimeout(() => {
      /* onSuccess would be handled by the user of service which would either return true or false
      * true - This means we need to continue polling
      * false - This means we need to stop polling
      */
      api
        .request(url)
        .then(response => {
          console.log('I was called', response);
          onSuccess(response);
        })
        .then(continuePolling => {
          _this.isPolling && continuePolling ? _this.runPolling() : _this.stopPolling();
        })
        .catch(error => {
          if (_this.config.shouldRetry && _this.config.retryCount > 0) {
            onFailure && onFailure(error);
            _this.config.retryCount--;
            _this.runPolling();
          } else {
            onFailure && onFailure(error);
            _this.stopPolling();
          }
        });
    }, interval);
  }

Пытаясь написать для него контрольные примеры, яЯ не очень уверен в том, как можно имитировать фальшивые таймеры и ответ API Axios.

Это то, что у меня есть до сих пор

import PollingService from '../PollingService';
import { statusAwaitingProduct } from '@src/__mock_data__/getSessionStatus';
import mockAxios from 'axios';

describe('timer events for runPoll', () => {
    let PollingObject,
    pollingInterval = 3000,
    url = '/session/status',
    onSuccess = jest.fn(() => {
      return false;
    });
    beforeAll(() => {
      PollingObject = new PollingService({
        url: url,
        interval: pollingInterval,
        onSuccess: onSuccess
      });
    });
    beforeEach(() => {
      jest.useFakeTimers();
    });
    test('runPolling should be called recursively when onSuccess returns true', async () => {
      expect.assertions(1);
      const mockedRunPolling = jest.spyOn(PollingObject, 'runPolling');
      const mockedOnSuccess = jest.spyOn(PollingObject.config, 'onSuccess');
      mockAxios.request.mockImplementation(
        () =>
          new Promise(resolve => {
            resolve(statusAwaitingProduct);
          })
      );

      PollingObject.startPolling();
      expect(mockedRunPolling).toHaveBeenCalledTimes(1);
      expect(setTimeout).toHaveBeenCalledTimes(1);
      expect(mockAxios.request).toHaveBeenCalledTimes(0);
      expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), pollingInterval);

      jest.runAllTimers();
      expect(mockAxios.request).toHaveBeenCalledTimes(1);
      expect(mockedOnSuccess).toHaveBeenCalledTimes(1);
      expect(PollingObject.isPolling).toBeTruthy();
      expect(mockedRunPolling).toHaveBeenCalledTimes(2);
    });
  });
});

Здесь даже при том, что mockedOnsuccess вызывается, но шут ожидать вызова не удаетсяговоря, что он вызывался 0 раз, а не 1 раз.

Может кто-нибудь помочь?Спасибо

1 Ответ

0 голосов
/ 22 сентября 2018

Проблема

Могут быть и другие проблемы с вашим тестом, но я рассмотрю конкретный вопрос, который вы задали по поводу expect(mockedOnSuccess).toHaveBeenCalledTimes(1);, с ошибкой 0 times:

jest.runAllTimers будетсинхронно запускать любые ожидающие обратные вызовы таймера, пока не останется больше.Это выполнит анонимную функцию, запланированную с setTimeout в runPolling.Когда анонимная функция выполняется, она вызовет api.request(url), но , и это все, что произойдет .Все остальное в анонимной функции содержится в обратных вызовах then, которые ставятся в очередь в PromiseJobs очереди заданий, представленной в ES6 .Ни одно из этих заданий не будет выполнено к тому времени, когда jest.runAllTimers вернется и тест продолжится.

expect(mockAxios.request).toHaveBeenCalledTimes(1); затем пройдет, так как api.request(url) выполнено.

expect(mockedOnSuccess).toHaveBeenCalledTimes(1);, затем произойдет сбой, так какthen обратный вызов, который вызвал бы его, все еще находится в очереди PromiseJobs и еще не выполнен.

Решение

Решение состоит в том, чтобы убедиться, что задания поставлены в очередь в PromiseJobsиметь возможность запустить, прежде чем утверждать, что mockedOnSuccess был вызван.

К счастью, очень легко разрешить выполнение любых ожидающих заданий в PromiseJobs в рамках теста async в Jest, просто вызовитеawait Promise.resolve();.По существу, это ставит в очередь оставшуюся часть теста в конце PromiseJobs и позволяет первым выполнить любые ожидающие задания в очереди:

test('runPolling should be called recursively when onSuccess returns true', async () => {
  ...
  jest.runAllTimers();
  await Promise.resolve();  // allow any pending jobs in PromiseJobs to execute
  expect(mockAxios.request).toHaveBeenCalledTimes(1);
  expect(mockedOnSuccess).toHaveBeenCalledTimes(1); // SUCCESS
  ...
}

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

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

Подробнее о том, как фальшивые таймеры и Обещания взаимодействуют здесь .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...