0

I have the following function in my controller:

function putActivities () {
    var promises = [];
    view.activities.forEach(function (activity) {
        promises.push(API.putActivities({activity: activity}).$promise);
    });
    $q.all(promises).then(function () {
        view.finished = true;
    });
}

where API is a $resource. This method is tested by the following snippet:

describe('putActivities', function() {
    beforeEach(function() {
        view.activities = ['hello'];
        spyOn(API, 'putActivities').and.callFake(function () {
            return {
                $promise: q(function () {})
            }
        });
    });
    it('view is finished after putting activities', function() {
        view.putActivities();
        expect(view.finished).toEqual(true);
    });
});

For some reason the $q.all never resolves and view.finished is never set to true. Does anyone know why this may be occurring and how I might fix it?

cscan
  • 3,684
  • 9
  • 45
  • 83

1 Answers1

1

The fake promises that the spy is returning need to be resolved (or rejected) manually.

Not sure what the q function in the spy does, but I will use Angular´s $q in the example:

beforeEach(function() {

  view.activities = ['hello'];
  mockedPromises = [];

  spyOn(API, 'putActivities').and.callFake(function() {

    var defer = $q.defer();
    mockedPromises.push(defer);

    return {
      $promise: defer.promise
    };
  });
});

Now you have access to the promises in your test and can either resolve or reject them, depending on which scenario you want to test:

it('view is finished after putting activities', function() {

  view.putActivities();

  mockedPromises.forEach(function(promise) {
    promise.resolve();
  });

  $rootScope.$apply();

  expect(view.finished).toEqual(true);
});

Note that when testing promises (at least with the $q service), the resolution of promises is tied to the digest cycle, which means that the then functions will only be called after a digest has run.

When testing you need to manually trigger the digest cycle (so you have better control of the flow). This is what the $rootScope.$apply() does.

tasseKATT
  • 38,470
  • 8
  • 84
  • 65
  • I had tried this at some point - but didn't continue on with this as it give me another exception, `Error: Unexpected request: GET http://localhost/api/endpoint. No more request expected`. There's a different API call made in the controller's init method which must have been invoked by the $rootScope.$apply (it also occurs with $rootScope.$digest). I tried spying on that other endpoint but that didn't resolve the error. – cscan Dec 17 '16 at 19:11
  • You need to expect the request via `$httpBackend`. See https://docs.angularjs.org/api/ngMock/service/$httpBackend – tasseKATT Dec 17 '16 at 19:13