admin管理员组文章数量:1394526
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);
}
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);
});
});
});
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
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);
}
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);
});
});
});
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
Share Improve this question edited Nov 5, 2018 at 11:49 skyboyer 23.8k7 gold badges62 silver badges71 bronze badges asked Sep 20, 2018 at 4:44 VivekNVivekN 1,60212 silver badges14 bronze badges1 Answer
Reset to default 7Issue
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
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);
then passes since api.request(url)
has executed.
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.
Solution
The solution is to make sure the jobs queued in PromiseJobs
have a chance to run before asserting that mockedOnSuccess
was called.
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
...
}
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.
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.
本文标签: javascriptTesting a Recursive polling function with jest and fake timersStack Overflow
版权声明:本文标题:javascript - Testing a Recursive polling function with jest and fake timers - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1744100220a2590839.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论