我创建了一个轮询服务,该服务以递归方式调用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);
}
在尝试为其编写测试用例时,我不确定如何模拟假计时器和axios api响应。
这是我到目前为止所拥有的
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次。
有人可以帮忙吗? 谢谢
答案 0 :(得分:1)
您的测试也可能有其他问题,但是我将解决您所提出的关于expect(mockedOnSuccess).toHaveBeenCalledTimes(1);
失败0 times
的特定问题:
jest.runAllTimers
将同步运行任何待处理的计时器回调,直到没有剩余的为止。这将执行在setTimeout
中用runPolling
安排的匿名函数。当匿名函数执行时,它将调用api.request(url)
,但是这就是所有发生的事情。匿名函数中的其他所有内容都包含在get queued in the PromiseJobs
Jobs Queue introduced with ES6的then
回调中。到jest.runAllTimers
返回时,这些作业都没有执行,测试将继续进行。
expect(mockAxios.request).toHaveBeenCalledTimes(1);
然后由于执行api.request(url)
而通过。
expect(mockedOnSuccess).toHaveBeenCalledTimes(1);
然后失败,因为可以调用它的then
回调仍在PromiseJobs
队列中并且尚未执行。
解决方案是在断言PromiseJobs
被调用之前,确保mockedOnSuccess
中排队的作业有运行的机会。
幸运的是,很容易允许PromiseJobs
中的所有待处理作业在async
的{{1}}测试中运行,只需调用Jest
。这实际上将其余测试排在await Promise.resolve();
的末尾,并允许队列中的所有未决作业先执行:
PromiseJobs
请注意,理想情况下,异步函数将返回Promise,然后测试可以等待。在您的情况下,您已安排了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来等待测试。
还要注意,您有多个链接的setTimeout
回调,因此您可能需要在测试期间多次then
中等待挂起的作业。
有关虚假计时器和承诺如何互动here的更多详细信息。