3
votes

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:

  • $timeout
  • settimeout
  • async function which calls await on function returning promise. Service import * as angular from 'angular';

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);

    });

});
2
Please, provide your real code instead of some abstract situation. I call a poll() function which keeps calling another function CheckTaskStatus() repeatedly using $interval - this will likely be treated totally different than SleepForASecond async function.Estus Flask
Updated the description.Phalgun

2 Answers

5
votes

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);
});
0
votes

You can ask the test case validation to wait using jasmine's Asynchronous Support.

Refer topic 'Asynchronous Support' under Jasmine documentation

In the above case mentioned, the test case should be changed to as below:

    //pass done as parameter to the 'it' call back function
    it('blah', function(done) {
       ...
       poll()
      // Mechanism for sleep
     SleepForASecond().then(function() {              
         expect();
         //jasmine will evaluate the test case only after done is called.
         done();
     });
    })