【问题标题】:How to mock Angular $q service in Jasmine test?如何在 Jasmine 测试中模拟 Angular $q 服务?
【发布时间】:2015-07-07 10:46:46
【问题描述】:

我正在尝试测试一个 Angular 服务,它有 2 个依赖项,一个在 $q 上,另一个在 'myService' 上,它也依赖于 $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
            };
        }
    ]);
})();

我开始编写 Jasmine 测试,如下所示,但是在模拟 $q 时遇到了问题。我想将 $q 的真实版本而不是模拟版本注入“myService”和“apiService”,但不知道如何实现。

'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();
    }));
});

编辑 这是基于以下响应的更新测试。我想我的问题是我认为我必须提供 $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();
    }));
}); 

【问题讨论】:

  • 你为什么要嘲笑$q?只使用真正的$q有什么问题

标签: javascript angularjs unit-testing jasmine angular-promise


【解决方案1】:

$scope.$apply() 的答案非常好,但我发现在每次使用 Promise 的测试中都要记住这样做很烦人。因此,由于我很懒,所以告诉 $q 像原生 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: {},
    })
  })
})

【讨论】:

    【解决方案2】:

    您必须使用$injector 服务才能获得真正的角度服务。

    $injector 用于检索提供者定义的对象实例, 实例化类型、调用方法和加载模块。

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

    【讨论】:

      【解决方案3】:

      你可以使用真正的$q。需要注意的是,您应该致电 $scope.$apply() 来解决承诺。

      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();
      });
      

      【讨论】:

      • 谢谢!我想我的问题是我认为我必须提供 $q。
      • 另一个重要提示:如果你的promise包含一个$http请求,你需要在$scope.$apply()之前执行你的$httpBackend.flush(),否则promise将无法正确解析。这仅在您将两者结合时才重要,这在大多数情况下是不必要的(因为 $http 一开始就被承诺),但我遇到了一个结合的实例,它需要这种方法。
      【解决方案4】:

      您应该在真正的$q 服务中使用。

      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(. . .)
      
      })
      

      【讨论】:

        猜你喜欢
        • 2014-11-22
        • 1970-01-01
        • 2017-08-02
        • 2014-09-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-05-04
        相关资源
        最近更新 更多