Pregunta

Im writing some unit tests for my controller which uses promises. Basically this:

UserService.getUser($routeParams.contactId).then(function (data) {
      $scope.$apply(function () {
      $scope.contacts = data;
   });
});

I have mocked my UserService. This is my unit test:

beforeEach(inject(function ($rootScope, $controller, $q, $routeParams) {
        $routeParams.contactId = contactId;
        window.localStorage.clear();
        UserService = {
            getUser: function () {
                def = $q.defer();
                return def.promise;
            }
         };
        spyOn(UserService, 'getUser').andCallThrough();


      scope = $rootScope.$new();
      ctrl = $controller('ContactDetailController', {
        $scope: scope,
        UserService:UserService
      });
  }));


it('should return 1 contact', function () {
    expect(scope.contacts).not.toBeDefined();
    def.resolve(contact);
    scope.$apply();

    expect(scope.contacts.surname).toEqual('NAME');
    expect(scope.contacts.email).toEqual('EMAIL');
});

This give me the following error:

Error: [$rootScope:inprog] $digest already in progress

Now removing the $scope.$apply in the controller causes the test to pass, like this:

UserService.getUser($routeParams.contactId).then(function (data) {
    $scope.contacts = data;     
 });

However this breaks functionality of my controller... So what should I do here?

Thanks for the replies, the $apply is not happening in the UserService. It's in the controller. Like this:

EDIT: The $apply is happening in the controller like this.

appController.controller('ContactDetailController', function ($scope, $routeParams, UserService) {
    UserService.getUser($routeParams.contactId).then(function (data) {
      $scope.$apply(function () {
        $scope.contacts = data;
    });
});

Real UserService:

 function getUser(user) {
    if (user === undefined) {
      user = getUserId();
    }
    var deferred = Q.defer();
    $http({
      method: 'GET',
      url: BASE_URL + '/users/' + user
    }).success(function (user) {
      deferred.resolve(user);
    });
    return deferred.promise;
  }
¿Fue útil?

Solución

There are a couple of issues in your UserService.

  • You're using Q, rather than $q. Hard to know exactly what effect this has, other than it's not typical when using Angular and might have affects with regards to exactly when then callbacks run.

  • You're actually creating a promise in getUser when you don't really need to (can be seen as an anti-pattern). The success function of the promise returned from $http promise I think is often more trouble than it's worth. In my experience, usually better to just use the standard then function, as then you can return a post-processed value for it and use standard promise chaining:

    function getUser(user) {
      if (user === undefined) {
        user = getUserId();
      }
      return $http({
        method: 'GET',
        url: BASE_URL + '/users/' + user
      }).then(function(response) {
        return response.data;
      });
    }
    

Once the above is changed, the controller code can be changed to

UserService.getUser($routeParams.contactId).then(function (data) {
  $scope.contacts = data;     
});

Then in the test, after resolving the promise call $apply.

def.resolve(contact);
scope.$apply();
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top