jhamm
jhamm

Reputation: 25022

How do I resolve a promise in an AngularJS unit test

I am using the basic karma/jasmine setup to test my Angular code. Here is my test:

var $controllerConstructor, ctr, mockSuperheroData, scope, deferred, q;

describe('main controller', function() {
   var $controllerConstructor, ctr, mockSuperheroData, scope, deferred, q;


   beforeEach(inject(function($controller, $rootScope, $q) {
     scope = $rootScope.$new();
     $controllerConstructor = $controller;
     q = $q;
     mockSuperheroData = {
       getSuperheroes: function() {
         deferred = q.defer();
         return deferred.promise;
       }
     };
     ctr = $controllerConstructor('MainCtrl', {$scope: scope, $location: {}, superheroService: mockSuperheroData, keys: {}});
   }));

   it('should set the result of getResource to scope.heroes', function() {
     scope.getHeroes();
     expect(scope.heroes).toBe(100);
   });
}

scope.getHeroes() calls the mockSuperheroData.getSuperheroes() which is returning a promise. How do I force the promise to return what I want in the unit test? Where can I hook into the promise to mock out its return?

Upvotes: 6

Views: 5264

Answers (2)

nackjicholson
nackjicholson

Reputation: 4839

var $controllerConstructor, ctr, mockSuperheroData, scope, deferred, q;

describe('main controller', function() {
  var $controllerConstructor, ctr, mockSuperheroData, scope, deferred, q, rootScope;


  beforeEach(inject(function($controller, $rootScope, $q) {
    scope = $rootScope.$new();
    $controllerConstructor = $controller;
    q = $q;
    rootScope = $rootScope;
    mockSuperheroData = {
      getSuperheroes: function() {
        deferred = q.defer();
        return deferred.promise;
      }
    };
    ctr = $controllerConstructor('MainCtrl', {$scope: scope, $location: {}, superheroService: mockSuperheroData, keys: {}});
  }));

  it('should set the result of getResource to scope.heroes', function() {
    scope.getHeroes();

    deferred.resolve(100);
    rootScope.$apply();

    expect(scope.heroes).toBe(100);
  });
});

It should be mentioned that because $q is integrated with $rootScope, not only do you need to resolve the deferred object, but you also need to propagate the changes by calling $rootScope.$apply.

I wrote a blog on mocking angular promises at projectpoppycock

and I wrote a working example, with tests, at plunkr

I really like the way you're injecting the mock service as a dependency of the controller by using the $controller service, I may need to revise my way of doing this. Your way is better :) Thanks!

Upvotes: 2

Bergi
Bergi

Reputation: 664196

How do I force the promise to return what I want in the unit test?

Basically you will need to call resolve on the Deferred:

deferred.resolve(100);

You can either put that directly before the return deferred.promise or in an asynchronous setTimeout.

Upvotes: 4

Related Questions