Angular.js: Promise.then() does not execute in Karma tests

Created on 12 Aug 2015  Â·  5Comments  Â·  Source: angular/angular.js

Steps to reproduce:

git clone [email protected]:angular/angular-seed.git

cd angular-seed

npm update

bower update

cat << 'EOF' > app/view1/promise_test.js
'use strict';
describe('Promise test', function() {
  beforeEach(module('myApp.view1'));
  it('should correclty execute a Promise.then', function(done) {
    inject(function($q) {
      var deferred = $q.defer();
      deferred.resolve();
      deferred.promise.then(function() {
        console.log('executing Promise.then');
        done();
      });
    });
  });
});
EOF

karma start

Karma fails with:
image

The problem seems to be that the $browser.defer.flush() gets never called in this scenario.

Interim fix — add this to the app/view1/view1.js (ideally put it in a file only Karma can see):

.config(function($provide) {
  $provide.decorator('$browser', function($delegate) {
    $delegate.defer = function(fn, delay) {
      setTimeout(fn, delay || 0);
    };
    return $delegate;
  });
})

The fix makes Karma work again:
image

No idea how this should be fixed proper, so not submitting a PR.

Most helpful comment

Hi @lgalfaso and thanks for the resolution. I of course went to great lengths to find the solution first, both on Google and StackOverflow. So hopefully this page will index differently and save other people the work and time.

As for the technical aspect, calling $rootScope.$digest() is limited to 10 iterations of immediate (synchronous) Promise executions, and doesn't work if there's more Promises scheduled asynchronously. Please consider the following test, where the second Promise.then won't get executed:

'use strict';
describe('Promise test', function() {
  beforeEach(module('myApp.view1'));
  it('should correclty execute a Promise.then', function(done) {
    inject(function($q, $rootScope) {

      var deferred = $q.defer();
      deferred.resolve();
      deferred.promise.then(function() {
        console.log('executing Promise.then 1');

        // this could be a new Promise scheduled by a 3rd party library function,
        // i.e. not possible to call another `$rootScope.$digest();` in between.
        setTimeout(function() {
          var deferred2 = $q.defer();
          deferred.promise.then(function() {
            console.log('executing Promise.then 2');
            done();
          });
        }, 500);

      });

      $rootScope.$digest();
    });
  });
});

Would this be something Angular devs would consider as a possible room for improvement?

All 5 comments

You need to call $rootScope.$digest() for promises to get resolved. Please keep in mind that this is not a support forum, please use StackOverflow to get support.

Hi @lgalfaso and thanks for the resolution. I of course went to great lengths to find the solution first, both on Google and StackOverflow. So hopefully this page will index differently and save other people the work and time.

As for the technical aspect, calling $rootScope.$digest() is limited to 10 iterations of immediate (synchronous) Promise executions, and doesn't work if there's more Promises scheduled asynchronously. Please consider the following test, where the second Promise.then won't get executed:

'use strict';
describe('Promise test', function() {
  beforeEach(module('myApp.view1'));
  it('should correclty execute a Promise.then', function(done) {
    inject(function($q, $rootScope) {

      var deferred = $q.defer();
      deferred.resolve();
      deferred.promise.then(function() {
        console.log('executing Promise.then 1');

        // this could be a new Promise scheduled by a 3rd party library function,
        // i.e. not possible to call another `$rootScope.$digest();` in between.
        setTimeout(function() {
          var deferred2 = $q.defer();
          deferred.promise.then(function() {
            console.log('executing Promise.then 2');
            done();
          });
        }, 500);

      });

      $rootScope.$digest();
    });
  });
});

Would this be something Angular devs would consider as a possible room for improvement?

In a test, you have to be in absolute control of when are things executed. This is why there are services like $timeout (and their ng-mock versions).

@youurayy did you ever find a good solution to this? I'm dealing with the same issue now and can find no solutions anywhere other than overriding $q with Q which I would rather not do.

@grahamj i have not

Was this page helpful?
0 / 5 - 0 ratings

Related issues

brijesh1ec picture brijesh1ec  Â·  3Comments

th0r picture th0r  Â·  3Comments

ashclarke picture ashclarke  Â·  3Comments

ceymard picture ceymard  Â·  3Comments

butchpeters picture butchpeters  Â·  3Comments