Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Jasmine Testing and Angular JS Promise-Returning Function

DZone's Guide to

Jasmine Testing and Angular JS Promise-Returning Function

Getting a timeout error when running a Jasmine test for your Angular JS promise-returning function? Let's see how we counter that!

· Web Dev Zone
Free Resource

Prove impact and reduce risk when rolling out new features. Optimizely Full Stack helps you experiment in any application.

This article originally appeared on my blog Codes And Notes, and is part of a series of posts called "Gotcha!" where I examine development mistakes my friends and I keep of tripping over regularly. Enjoy! 

So, imagine you are writing a Jasmine test for your Angular JS factory component, which returns a promise generated by our beloved $q. You know how to test an asynchronous response with Jasmine. You confidently run the test and... bam, you get an error message:

"Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL".

The factory function code resembles something like this: 

function myFunction() {
    var defer = $q.defer();
    defer.resolve('yay!');
    return defer.promise;
}


And your Jasmine test looks like this:

describe('MyService tests', function () {
    var MyService;

    beforeEach(module('MyModule'));
    beforeEach(inject(function (_MyService_) {
        MyService = _MyService_;
    }));

    describe('when working with MyService', function () {
        it('should return "yay!"', function (done) {
            MyService.myFunction().then(function (result) {
                expect(result).toBeDefined();
                expect(result).toEqual('yay!');
                done();
            });
        });
    });
});


You would expect for the test to enter the promise’s resolution function (the “then()“) and to eventually execute the done() function, to signify Jasmine that the asynchronous execution is complete.

Alas, no matter how much you curse your machine, the test always times out with the same message. In fact, you quickly discover that the handling of the promise’s resolving is never executed, so the done() function is never executed.

It's Probably...

You start looking at the obvious, such as typos, or whether you are indeed returning a promise of the deferred object (I cannot count how many times I have returned deferinstead of defer.promise). 

However you probably already know it would raise a “.then is not a function” message instead, so that can be dismissed.

You quickly do a test within your Angular JS application: the function behaves correctly and returns a promise which resolves as expected!

You eventually start wondering whether Jasmine is capable of understanding a $q.defer().promise object, or whether there’s some difference in the implementation of promises between Angular JS and JavaScript. Nonsense!

Gotcha!

Okay, there is one small difference between Jasmine’s context and your application’s AngularJS context: the AngularJS context has its own life cycle!

In the test, we never enter into the resolution function because the promise is actually never resolved. That’s because the execution of the resolution in our tested function is actually scheduled to be executed on the application’s next digest.

Within our application, the life cycle eventually enters the $digest loop and processes the promise resolution. But our Jasmine tests are running outside of AngularJS, so the $digest loop is never executed and our promise is never resolved.

So, we basically need to force the $digest to run. One possible way of achieving that is changing your test this way:

describe('MyService tests', function () {
    var MyService;
    var rootScope;

    beforeEach(module('MyModule'));
    beforeEach(inject(function (_$rootScope_, _MyService_) {
        MyService = _MyService_;
        rootScope = _$rootScope_;
    }));

    describe('when working with MyService', function () {
        it('should return "yay!"', function (done) {
            MyService.myFunction().then(function (result) {
                expect(result).toBeDefined();
                expect(result).toEqual('yay!');
                done();
            });
            rootScope.$digest();
        });
    });
});


To be frank, this case was already documented before. You can find reports on it here, a JSFiddle courtesy of Witold Szczerba, and a Stack Overflow entry here. Check them out for more details on the Angular JS mechanisms at work. 

I should know this one by now... and yet, you wouldn’t believe how many times I get pwned by this! Hence this small reminder. 

Until next time... Cheers! 

With SDKs for all major client and server side platforms, you can experiment on any platform with Optimizely Full Stack.

Topics:
gotcha ,javascript ,angularjs ,jasmine ,testing

Published at DZone with permission of Diego Pappalardo. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}