Usecase: For my angularJS(1.6.4) application, I am writing unit test in jasmine.
In my test case, I call a poll() function which keeps calling another function CheckTaskStatus() repeatedly using $interval. I am spy-ing CheckTaskStatus() and want to check that it has been called a certain number of times. So after calling poll(), I want to be able to wait for some time and then check the the number of times CheckTaskStatus() is called using expect().
Problem: I have been unable to find a way to make jasmine wait after call to poll() and before expect().
After poll() I have tried using below options, but those do not cause jasmine to sleep:
export class Polling { public static $inject = ['$q', '$http', '$interval'];
constructor(private $q: ng.IQService, private $http, private $interval) {}
public poll(interval: number, pollFn: () => ng.IPromise<any>, until?: (any) => boolean, cancel?: ng.IPromise<any>) {
let intervalPromise = null;
const done = this.$q.defer();
const intervalFn = () => {
pollFn().then((pollFnResult) => {
if (until && until(pollFnResult)) {
this.$interval.cancel(intervalPromise);
done.resolve();
}
}).catch(() => {});
};
// Set up an interval to execute the pollFunction
intervalPromise = this.$interval(intervalFn, interval);
intervalPromise.catch(() => {});
intervalFn();
if (cancel) {
cancel.then(() => {
this.$interval.cancel(intervalPromise);
done.resolve();
})
.catch(() => {});
}
return done.promise;
}
}
export default angular.module('myPolling', []) .service('polling', Polling).name;
Jasmine test
fdescribe('myPolling module tests', () => {
'use strict';
let $rootScope,
$q: ng.IQService,
$http,
$interval,
$timeout;
beforeEach(mockModule('myPolling'));
beforeEach(() => {
jasmine.clock().install();
});
beforeEach(inject((_$rootScope_, _$q_, _$http_, _$interval_, _polling_, _$timeout_) => {
this.$rootScope = _$rootScope_;
this.$q = _$q_;
this.$http = _$http_;
this.$interval = _$interval_;
this.polling = _polling_;
$timeout = _$timeout_;
}));
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function SleepForASecond() {
await sleep(1050);
}
it('blah', () => {
let pollCount = 0;
let pollObj = {
pollFn: () => {
pollCount++;
return this.$q.when(pollCount);
}
};
// jasmine.createSpy('pollFn');
spyOn(pollObj, 'pollFn').and.callThrough();
let cancel = this.$q.defer();
this.polling.poll(100, pollObj.pollFn, () => false, cancel.promise);
// Need a mechanism to wait for a second.
// SleepForASecond().then(() => { cancel.resolve(); });
expect(pollObj.pollFn).toHaveBeenCalledTimes(10);
});
});
AngularJS asynchronous code and arbitrary JS code are tested differently in Jasmine.
AngularJS services were designed specifically to make tests synchronous, including $interval
:
In tests you can use $interval.flush(millis) to move forward by millis milliseconds and trigger any functions scheduled to run in that time.
So it is:
let cancel = this.$q.defer();
this.polling.poll(100, pollObj.pollFn, () => false, cancel.promise);
$interval.flush(1050);
expect(pollObj.pollFn).toHaveBeenCalledTimes(10);
It's different in tests that involve non-Angular asynchronous units. Jasmine clock API should be used instead. It patches built-in timer functions in order to execute them synchronously with Jasmine tick
method.
beforeEach(() => {
jasmine.clock().install();
});
afterEach(() => {
jasmine.clock().uninstall();
});
it('...', () => {
SleepForASecond().then(() => {
expect(1).toBe(0);
});
jasmine.clock().tick(1050);
});
Jasmine 2.7 adds support for promises and async
functions. This allows to seamlessly test promise-based functions, but promises will produce a real delay and result in asynchronous test:
it('...', async () => {
await SleepForASecond();
expect(1).toBe(0);
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With