Angular.js

Learning Angular: Testing $q promise resolves with Sinon and Jasmine

This article shows a brief example on how to properly mock and resolve a $q promise from within a Jasmine unit test.

This article is part of my “Learning NG” series, presenting some of my adventures while learning Angular. Check out the series intro and other articles. Note, I’m an Angular newbie, so I’m more than happy for any kind of feedback and improvement suggestions from more experienced people than me.

Problem

I have a service, let’s call it consumerService which uses another service personService to make an asynchronous call. In the case of a successful response, it calls a method on a 3rd service, called helloWorldService. What I’d like to test is the fact that the helloWorldService gets called upon a successful call of the personService.

Roughly, the code looks like this:

app.factory('consumerService', function(personService, helloWorldService){
  var service = {
    doSomething: doSomething
  };
  return service;

  function doSomething(){
    personService.get()
      .then(function(){
        // I want to test this one here
        helloWorldService.sayHello();
      })
  }

});

To test it, I need to stub the personService.get() function s.t. it positively resolves the promise it returns.

Solution

For stubbing, we can use Sinon.js. The first param is the object on which to operate upon, then the name of the function to stub and as the 3rd param I pass in the function that gets called instead of the original one.

sandbox.stub(personService, 'get', function(){
    var deferred = $q.defer();

    // immediately resolve it
    deferred.resolve();

    return deferred.promise;
  });

As you can see, the stubbed function creates a new deferred, using Angular’s $q service and immediately resolves it.

Note, in this particular test we’re not interested in a specific return value, otherwise you’d have to pass that desired value to the .resolve(...) function.

Then we also stub the helloWorldService.sayHello function to be able to listen to the number of invocations.

...
sandbox.stub(helloWorldService, 'sayHello');

Then invoke the SUT (subject under test):

...
consumerService.doSomething();

Finally, the expectations:

$scope.$apply();
expect(helloWorldService.sayHello.calledOnce).toBeTruthy();

What does that $scope.$apply() do there?? That’s the important piece actually. If you don’t call it, your promise won’t get resolved. The Angular documentation emphasizes this: https://docs.angularjs.org/api/ng/service/$q.

So, all wrapped up (highlighting the important pieces):

var sandbox;
var $scope;
...

describe('using the person service', function() {

  beforeEach(inject(function($rootScope, _consumerService_, _personService_, _helloWorldService_) {
      sandbox = sinon.sandbox.create();
      $scope = $rootScope.$new();
      ...
    }));

    afterEach(function(){
      sandbox.restore();
    });

    ...

    it('should properly resolve the promise', inject(function($q) {
      sandbox.stub(personService, 'get', function(){
        var deferred = $q.defer();

        // immediately resolve it
        deferred.resolve();

        return deferred.promise;
      });
      sandbox.stub(helloWorldService, 'sayHello');

      consumerService.doSomething();

      // THIS IS THE IMPORTANT CALL HERE
      $scope.$apply();

      expect(helloWorldService.sayHello.calledOnce).toBeTruthy();
    }));
});

You can try it live in the Plunkr below.

Juri Strumpflohner

Juri Strumpflohner mainly operates in the web sector developing rich applications with HTML5 and JavaScript. Beside having a Java background and developing Android applications he currently works as a software architect in the e-government sector. When he’s not coding or blogging about his newest discoveries, he is practicing Yoseikan Budo where he owns a 2nd DAN.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button