简体   繁体   中英

How to mock Angular $q service in Jasmine test?

I am trying to test an Angular service, which has 2 dependencies, one on $q and another one on 'myService' which also has dependency on $q.

(function() {
    'use strict';

    angular.module('myModule').factory('myService', [
        '$q',
        'apiService',
        function($q, apiService) {

            var data = null;

            function getData() {
                var deferred = $q.defer();

                if (data === null) {

                    apiService.get('url').then(function(result) {
                        data = result;
                        deferred.resolve(data);
                    }, function() {
                        deferred.reject();
                    });
                } else {
                    deferred.resolve(data);
                }

                return deferred.promise;
            }

            return {
                getData: getData
            };
        }
    ]);
})();

I started writing a Jasmine test as seen below, but and having issues mocking $q. I would like to inject the real version of $q instead of the mock version to 'myService' and 'apiService', but am not sure how to accomplish that.

'use strict';

describe('My service', function() {
    var qSpy, apiServiceSpy;

    beforeEach(module('myModule'));

    beforeEach(function() {
        qSpy = jasmine.createSpyObj('qSpy', ['defer']);

        apiServiceSpy = jasmine.createSpyObj('apiServiceSpy', ['get']);
        apiServiceSpy.get.and.callFake(function() {
            var deferred = $q.defer();
            deferred.resolve('Remote call result');
            return deferred.promise;
        });

        module(function($provide) {
            $provide.value('$q', qSpy);
            $provide.value('apiService', apiServiceSpy);
        });
    });

    it('should get data.', inject(function(myService) {
        // Arrange

        // Act
        var data = myService.getData();

        // Assert
        expect(data).not.toBeNull();
    }));
});

Edit Here is the updated test based on the responses below. I guess my issue was I assumed I had to provide $q.

'use strict';

describe('My service', function() {
    var service, apiServiceSpy;

    beforeEach(module('myModule'));

    beforeEach(function() {
        apiServiceSpy = jasmine.createSpyObj('apiServiceSpy', ['get']);

        module(function($provide) {
            $provide.value('apiService', apiServiceSpy);
        });
    });

    beforeEach(inject(function($q, myService) {
        service = myService;

        apiServiceSpy.get.and.callFake(function() {
            var deferred = $q.defer();
            deferred.resolve('Remote call result');
            return deferred.promise;
        });
    }));

    it('should get data.', function() {
        // Arrange

        // Act
        var data = service.getData();

        // Assert
        expect(data).not.toBeNull();
    }));
}); 

You can use the real $q. Important to note, that you should call $scope.$apply() to resolve promises.

var service;
var $scope;
beforeEach(function() {

    angular.mock.module('app', function ($provide) {
        $provide.value('apiService', apiServiceSpy);
    });

    angular.mock.inject(function (_myService_, _$rootScope_) {
        service = _myService_;
        $scope = _$rootScope_;
    });
});

it('works like a charm', function() {
    var data;
    service.getData().then(function(d) {
        data = d;
    });
    $scope.$apply();  // resolve promise
    expect(data).toBeAwesomeData();
});

You have to use the $injector service to get the real angular service.

$injector is used to retrieve object instances as defined by provider, instantiate types, invoke methods, and load modules.

var $q
beforeEach(inject(function($injector) {
    $q = $injector.get('$q');
}));

The $scope.$apply() answer is awfully good, but I find it annoying to remember to do that in every test that uses promises. So since I'm lazy tell $q to act like native Promises.

beforeEach(function() {
  module('my.module')
  module(function($provide) {
    $provide.factory('$q', function() {
      // Make $q act like Promise, but with the $q() constructor
      function $q(resolve, reject) {
        return new Promise(resolve, reject)
      }

      Object.getOwnPropertyNames(Promise).forEach(function(name) {
        $q[name] = Promise[name]
      })

      return $q
    })
  })

  inject(function(_$controller_, _$q_ /* ... more dependencies */) {
    controller = _$controller_('NameOfYourController', {
      $q: _$q_,
      $scope: {},
    })
  })
})

You should use in the real $q service.

var rootScope;
var deferrred;
var fakeMyService = { getData: function() { return deferred.promise}};

beforEach(inject(function($q, $rootScope) {
     deferred = $q.defer();
     rootScope = $rootScope;
}))

it('should do something', function() {
    . . .        

    deferred.resove(<something>);
    $rootScope.$digest();

    . . .
    expect(. . .)

})

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM