使用玩笑和假计时器测试递归轮询功能 [英] Testing a Recursive polling function with jest and fake timers
问题描述
我创建了一个轮询服务,该服务以递归方式调用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屋!