Roswell Studios

139 Fulton Street, Ste 132
New York, NY 10038

Jasmine Hack: $q

— July 22, 2016

Lets say you have a Jasmine test of an asynchronous call with then(). You need to call $rootScope.$digest() in order for the then() to happen.

Lets say you are testing the bit that returns that promise. You can’t expect($q.reject).toHaveBeenCalled() because the reject inside your function is not the same thing as $q.reject, which is the only thing spyOn can see. You could fix that by calling $digest, running $q, and getting the results from inside then().

But is it really necessary?

describe("map", function () {
  var Map, $ionicLoading, $q

  beforeEach(function () {
    module('xxx-library')
    module(function ($provide) {
      $provide.value('$ionicLoading', $ionicLoading = jasmine.createSpyObj('$ionicLoading', ['hide', 'show']))
      $q = {//fake $q Advantage: no digest loop
        resolve: jasmine.createSpy('resolve'),//don't wait for it to actually process
        reject: jasmine.createSpy('reject')
      }

      $provide.value('$q', function(callback) {
        callback($q.resolve, $q.reject); return $q}
      )//note the angular q is not the same as the jasmine q
    })

    inject(function ($injector) {
      Map = $injector.get('Map')
      //$q = $injector.get('$q')//real $q
    })

  })

})

 

 

As with the ionicLoading hide/show calls, it doesn’t actually matter what the after-effects of the resolve/reject call are, just that it got called. Fun thing: the $q object seen by Jasmine is not the same $q object inside the Angular code. This enables the “$q(function(resolve, reject){})” to use the jasmine spies.

So to test this:


getCoordsByAddress: function (address) {
return $q(function (resolve, reject) {
if (!address || address.length < 5) {
reject('too short');
return;
}

We can do


describe('getCoordsByAddress', function () {
it('should reject for no address', function () {
var q = Map.getCoordsByAddress()
expect(q).toEqual(jasmine.any(Object))
expect($q.reject).toHaveBeenCalled()
expect($q.resolve).not.toHaveBeenCalled()
})

it(‘should reject for too short’, function () {
expect(Map.getCoordsByAddress(‘spoo’)).toEqual(jasmine.any(Object))
expect($q.reject).toHaveBeenCalledWith(‘too short’)
expect($q.resolve).not.toHaveBeenCalled()
})
})

})

The tests are clean and logical and doesn’t run the $digest startup or loop, or any asynchronous calls.
The test has both q and $q, but its the same object. You could clean that up either way: look at $q and not the results, or don’t even bother with the var $q and only use the results.

$q.defer() is similar but left as an exercise for the reader.

Back to all