【问题标题】:How to unit test (using Jasmine) a function in a controller which calls a factory service which returns a promise如何单元测试(使用 Jasmine)控制器中的函数,该函数调用返回承诺的工厂服务
【发布时间】:2016-07-27 06:18:35
【问题描述】:

在下面的 SampleController 中,我如何对 postAttributes 函数调用 sampleService.updateMethod。由于 updateMethod 返回了 promise,我遇到了麻烦。

    angular.module('sampleModule')
       .controller('SampleController', SampleController);

    SampleController.$inject =['sampleService'];

    function SampleController(sampleService){

    this.postAttributes = function() {    
        sampleService.updateMethod(number,attributes)
            .then(function(response){
                //do something on successful update
            },function(response){
                //do something on unsuccessful update
            });
        }; 

    }

这是我的工厂服务:

    angular.module('sampleModule')
        .factory('sampleService', sampleService);

    sampleService.$inject = ['$http'];

    function sampleService($http) {
        return {
            getMethod: function(acctNumber){
                return $http({
                    method: 'GET',
                    url: //api endpoint
                });
            },
            updateMethod: function(number, attributes){
                return $http({
                    method: 'PUT',
                    url: //api endpoint,
                    data: //payload
                });
            }
        };
    }

我想在控制器规范中模拟工厂服务,而不是将实际服务直接注入 $controller,因为大多数单元测试指南都指定在隔离下测试单元。

示例控制器规格:

    describe('SampleController Test', function(){
        var $controller;
        var service;

        beforeEach(angular.mock.module('sampleModule')); 

        beforeEach(angular.mock.inject(function(_$controller_){
            $controller = _$controller_;
        }));

        it('Testing $scope variable', function(){
            var sampleController = $controller('SampleController', { 
                sampleService: service, //mocked factory service 
            });

            sampleController.postAttributes(); //calling the function first
            //here I would like to make an assertion to check if
            //sampleService.updateMethod has been called with certain parameters                        
            //how do I do that??
        });

    });

【问题讨论】:

    标签: angularjs unit-testing jasmine angularjs-factory angular-mock


    【解决方案1】:

    谷歌搜索并找到了模拟工厂服务的解决方案,并遵循@TehBeardedOne 之类的承诺创建方法,并使其从模拟服务中返回。

        describe('SampleController', function(){
    
            var mockService, controller, deferred, $rootScope;
    
            beforeEach(function(){
    
                angular.mock.module('sampleModule');
    
                angular.mock.module(function($provide){
                    $provide.factory('sampleService', function($q){
                        function updateMethod(acct, attr){
                            deferred = $q.defer();
                            return deferred.promise;
                        }
                        return{updateMethod: updateMethod};
                    });
                });
    
                angular.mock.inject(function($controller, sampleService, _$rootScope_){
                    $rootScope = _$rootScope_;
                    mockService = sampleService;
                    spyOn(mockService, 'updateMethod').and.callThrough();
                    controller =$controller('SampleController', {
                        sampleService: mockService,
                    })
                });
            });
    
            it('postAttributes function should call updateMethod', function(){
    
                controller.postAttributes();
                expect(mockService.updateMethod).toHaveBeenCalled();
                expect(mockService.updateMethod).toHaveBeenCalledWith(controller.accountNumber, controller.attributes);
            });
    
            it('postAttributes success block', function(){
                controller.postAttributes();
                var res = {
                    data: '2323'
                }
                deferred.resolve(res);
                $rootScope.$digest();
                expect(//something in success block).toBe(something);
            });
    
            it('postAttributes failure block', function(){
                controller.postAttributes();
                var res = {
                    data: '9898'
                }
                deferred.reject(res);
                $rootScope.$digest();
                expect(controller.lame).toBe('dont type shit');
            });
    
        });
    

    我用 $provider 服务模拟了 sampleService 并制作了 updateMethod 使用 $q 返回一个承诺。稍后您可以解决或拒绝承诺,并在各个 it 块中测试成功和失败块。

    【讨论】:

      【解决方案2】:

      您需要使用 spy 来检查函数是否被调用。有几种方法可以做到这一点,我相信你可能已经注意到了。您还需要注入$q$rootScope,因为您需要循环摘要循环才能返回承诺。

      事实上,如果你只想检查函数是否被调用,一个简单的 spy 就可以正常工作。你甚至不必兑现承诺。话虽如此,如果您想继续通过postAttributes() 函数并测试此函数中的其他内容,那么您将需要返回承诺来执行此操作。这是适合我的方法。这样的事情应该可以工作。

      describe('SampleController Test', function(){
          var $controller;
          var service;
          var $q;
          var $rootScope;
          var updateMethodDeferred;
          var ctrl;
          var mockHttpObject;
      
          beforeEach(angular.mock.module('sampleModule'));
      
          beforeEach(angular.mock.inject(function(_sampleService_, _$q_, _$rootScope_, _$controller_){
              service = _sampleService_;
              $q = _$q_;
              $rootScope = _$rootScope_;
              $controller = _$controller_;
      
              mockHttpObject = {
                  //add mock properties here
              }
      
              updateMethodDeferred = $q.defer();
              spyOn(service, 'updateMethod').and.returnValue(updateMethodDeferred.promise);
      
              ctrl = $controller('SampleController', {
                  sampleService: service, //mocked factory service
              });
          }));
      
          it('Testing $scope variable', function(){
              ctrl.postAttributes();
              expect(service.updateMethod).toHaveBeenCalled();
          });
      
          it('Should do something after promise is returned', function(){
              ctrl.postAttributes();
              updateMethodDeferred.resolve(mockHttpObject);
              $rootScope.$digest();
              expect(/*something that happened on successful update*/).toBe(/*blah*/)           
          });
      });
      

      【讨论】:

      • 尝试了上述方法,但后来发现 sampleService 本身有很多依赖项。因此,如果我必须遵循上述方法,那么我还需要注入 sampleService 的所有依赖项,这应该是这种情况,因为我们只测试控制器,我们应该模拟所有依赖项。目前我正在尝试使用 $provide.factory 模拟该服务。如何通过成功和错误块从模拟服务返回承诺?!
      • 我不确定您为什么还需要为服务注入依赖项。我从来没有这样做过,我的一些服务也有几个依赖项。在任何情况下,您都可以使用 deferred.resolve() 来命中成功块,并使用 deferred.reject() 来命中错误块。
      猜你喜欢
      • 2017-04-07
      • 2015-03-05
      • 2013-11-03
      • 2018-01-21
      • 2014-05-19
      • 2016-12-18
      • 2017-05-26
      • 2016-06-17
      • 2016-10-04
      相关资源
      最近更新 更多