Jasmine test on AngularJS promise-returning function keeps on timing out

The code goes might resemble a bit like this:

And your Jasmine test might look like:

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 message “Timeout – Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL“. 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. Don’t laugh too much, how many times I have returned defer instead 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 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 AngularJS and JavaScript.

Nonsense!
But there’s a tiny bit of truth in there…

Gotcha!

Indeed, 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:

This case was already documented before. You can find reports of this here and a JSFiddle courtesy of Witold Szczerba, and a Stack Overflow entry here.

I should know this one by now. And yet you wouldn’t believe how many times I get pwned by this… which is why I thought I’d post about it again!

Until next time… Cheers!

 

Leave a Reply

Your email address will not be published. Required fields are marked *