使用玩笑和假计时器测试递归轮询功能 [英] Testing a Recursive polling function with jest and fake timers

查看:149
本文介绍了使用玩笑和假计时器测试递归轮询功能的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我创建了一个轮询服务,该服务以递归方式调用api,并且在满足特定条件的情况下,如果api成功,将继续进行轮询.

I have created a polling service which recursively calls an api and on success of the api if certain conditions are met, keeps polling again.

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

在尝试为其编写测试用例时,我不确定如何模拟假计时器和axios api响应.

While trying to write the test cases for it, I am not very sure as to how can simulate fake timers and the axios api response.

这是我到目前为止所拥有的

This is what I have so far

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次.

Here even though mockedOnsuccess is called but jest expect call fails saying it was called 0 times instead being called 1times.

有人可以帮忙吗? 谢谢

Can someone please help? Thanks

推荐答案

问题

您的测试也可能有其他问题,但是我将解决您询问的有关expect(mockedOnSuccess).toHaveBeenCalledTimes(1);失败的问题,该问题是0 times:

Issue

There might be other issues with your test as well, but I will address the specific question you asked about expect(mockedOnSuccess).toHaveBeenCalledTimes(1); failing with 0 times:

jest.runAllTimers将同步运行任何暂挂的计时器回调,直到没有剩余的为止.这将执行runPolling中的setTimeout安排的匿名函数.当匿名函数执行时,它将调用api.request(url),但是那将发生的一切.匿名函数中的所有其他内容都包含在

jest.runAllTimers will synchronously run any pending timer callbacks until there are no more left. This will execute the anonymous function scheduled with setTimeout within runPolling. When the anonymous function executes it will call api.request(url) but that is all that will happen. Everything else in the anonymous function is contained within then callbacks that get queued in the PromiseJobs Jobs Queue introduced with ES6. None of those jobs will have executed by the time jest.runAllTimers returns and the test continues.

expect(mockAxios.request).toHaveBeenCalledTimes(1);然后通过,因为执行了api.request(url).

expect(mockAxios.request).toHaveBeenCalledTimes(1); then passes since api.request(url) has executed.

expect(mockedOnSuccess).toHaveBeenCalledTimes(1);然后失败,因为可以调用它的then回调仍在PromiseJobs队列中并且尚未执行.

expect(mockedOnSuccess).toHaveBeenCalledTimes(1); then fails since the then callback that would have called it is still in the PromiseJobs queue and hasn't executed yet.

解决方案是确保在断言mockedOnSuccess被调用之前,在PromiseJobs中排队的作业有机会运行.

The solution is to make sure the jobs queued in PromiseJobs have a chance to run before asserting that mockedOnSuccess was called.

幸运的是,很容易允许PromiseJobs中的所有待处理作业在Jest中的async测试中运行,只需调用await Promise.resolve();.这实际上将其余测试排在PromiseJobs的末尾,并允许队列中的所有未决作业先执行:

Fortunately, it is very easy to allow any pending jobs in PromiseJobs to run within an async test in Jest, just call await Promise.resolve();. This essentially queues the rest of the test at the end of PromiseJobs and allows any pending jobs in the queue to execute first:

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
  ...
}

请注意,理想情况下,异步函数将返回Promise,然后测试可以等待该Promise.在您的情况下,您已为setTimeout安排了回调,因此无法返回Promise来等待测试.

Note that ideally an asynchronous function will return a Promise that a test can then wait for. In your case you have a callback scheduled with setTimeout so there isn't a way to return a Promise for the test to wait on.

还请注意,您具有多个链接的then回调,因此在测试期间您可能需要多次等待PromiseJobs中的待处理作业.

Also note that you have multiple chained then callbacks so you may need to wait for the pending jobs in PromiseJobs multiple times during your test.

有关虚假计时器和承诺如何交互的更多详细信息,请此处.

More details about how fake timers and Promises interact here.

这篇关于使用玩笑和假计时器测试递归轮询功能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆