Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/javascript/362.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/angularjs/25.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript 使用success()和error()测试控制器_Javascript_Angularjs_Unit Testing_Jasmine - Fatal编程技术网

Javascript 使用success()和error()测试控制器

Javascript 使用success()和error()测试控制器,javascript,angularjs,unit-testing,jasmine,Javascript,Angularjs,Unit Testing,Jasmine,我正试图找出在控制器中实现单元测试成功和错误回调的最佳方法。我可以模拟服务方法,只要控制器只使用默认的$q函数,比如“then”(参见下面的示例)。当控制器响应“成功”或“错误”承诺时,我遇到问题。(如果我的术语不正确,很抱歉) 下面是一个示例控制器\服务 var myControllers = angular.module('myControllers'); myControllers.controller('SimpleController', ['$scope', 'myService'

我正试图找出在控制器中实现单元测试成功和错误回调的最佳方法。我可以模拟服务方法,只要控制器只使用默认的$q函数,比如“then”(参见下面的示例)。当控制器响应“成功”或“错误”承诺时,我遇到问题。(如果我的术语不正确,很抱歉)

下面是一个示例控制器\服务

var myControllers = angular.module('myControllers');

myControllers.controller('SimpleController', ['$scope', 'myService',
  function ($scope, myService) {

      var id = 1;
      $scope.loadData = function () {
          myService.get(id).then(function (response) {
              $scope.data = response.data;
          });
      };

      $scope.loadData2 = function () {
          myService.get(id).success(function (response) {
              $scope.data = response.data;
          }).error(function(response) {
              $scope.error = 'ERROR';
          });
      }; 
  }]);


cocoApp.service('myService', [
    '$http', function($http) {
        function get(id) {
            return $http.get('/api/' + id);
        }
    }
]);  
我有以下测试

'use strict';

describe('SimpleControllerTests', function () {

    var scope;
    var controller;
    var getResponse = { data: 'this is a mocked response' };

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

    beforeEach(angular.mock.inject(function($q, $controller, $rootScope, $routeParams){

        scope = $rootScope;
        var myServiceMock = {
            get: function() {}
        };

        // setup a promise for the get
        var getDeferred = $q.defer();
        getDeferred.resolve(getResponse);
        spyOn(myServiceMock, 'get').andReturn(getDeferred.promise);

        controller = $controller('SimpleController', { $scope: scope, myService: myServiceMock });
    }));


    it('this tests works', function() {
        scope.loadData();
        expect(scope.data).toEqual(getResponse.data);
    });

    it('this doesnt work', function () {
        scope.loadData2();
        expect(scope.data).toEqual(getResponse.data);
    });
});
第一个测试通过,第二个测试失败,错误为“TypeError:对象不支持属性或方法‘success’”。在这种情况下,我得到的结果是,我保证 没有成功的功能。好的,问题是,编写这个测试的好方法是什么,这样我就可以测试模拟服务的“成功”、“错误”和“然后”条件

我开始认为我应该避免在控制器中使用success()和error()

编辑

因此,经过进一步思考,并感谢下面的详细答案,我得出结论,在控制器中处理成功和错误回调是不好的。正如HackedByChinese在下面提到的,成功\错误是由$http添加的语法糖。所以,实际上,通过尝试处理success\error,我让$http关注点泄漏到我的控制器中,这正是我试图通过将$http调用包装到服务中来避免的。我将采取的方法是将控制器更改为不使用success\error:

myControllers.controller('SimpleController', ['$scope', 'myService',
  function ($scope, myService) {

      var id = 1;
      $scope.loadData = function () {
          myService.get(id).then(function (response) {
              $scope.data = response.data;
          }, function (response) {
              $scope.error = 'ERROR';
          });
      };
  }]);
这样,我可以通过对延迟对象调用resolve()和reject()来测试error\success条件:

'use strict';

describe('SimpleControllerTests', function () {

    var scope;
    var controller;
    var getResponse = { data: 'this is a mocked response' };
    var getDeferred;
    var myServiceMock;

    //mock Application to allow us to inject our own dependencies
    beforeEach(angular.mock.module('myApp'));
    //mock the controller for the same reason and include $rootScope and $controller
    beforeEach(angular.mock.inject(function($q, $controller, $rootScope, $routeParams) {

        scope = $rootScope;
        myServiceMock = {
            get: function() {}
        };
        // setup a promise for the get
        getDeferred = $q.defer();
        spyOn(myServiceMock, 'get').andReturn(getDeferred.promise);
        controller = $controller('SimpleController', { $scope: scope, myService: myServiceMock });  
    }));

    it('should set some data on the scope when successful', function () {
        getDeferred.resolve(getResponse);
        scope.loadData();
        scope.$apply();
        expect(myServiceMock.get).toHaveBeenCalled();
        expect(scope.data).toEqual(getResponse.data);
    });

    it('should do something else when unsuccessful', function () {
        getDeferred.reject(getResponse);
        scope.loadData();
        scope.$apply();
        expect(myServiceMock.get).toHaveBeenCalled();
        expect(scope.error).toEqual('ERROR');
    });
});

正如有人在删除的答案中提到的,
success
error
是由
$http
添加的语法糖,因此当你创建自己的承诺时,它们并不存在。您有两个选择:

1-不要模拟服务并使用
$httpBackend
设置期望值和刷新 这样做的目的是让你的
myService
在不知道它正在被测试的情况下正常运行
$httpBackend
将允许您设置期望和响应,并刷新它们,以便您可以同步完成测试<代码>$http不会更明智,它返回的承诺看起来和功能都会像一个真实的承诺。如果您有一些HTTP期望值很少的简单测试,那么这个选项很好

'use strict';

describe('SimpleControllerTests', function () {

    var scope;
    var expectedResponse = { name: 'this is a mocked response' };
    var $httpBackend, $controller;

    beforeEach(module('myApp'));

    beforeEach(inject(function(_$rootScope_, _$controller_, _$httpBackend_){ 
        // the underscores are a convention ng understands, just helps us differentiate parameters from variables
        $controller = _$controller_;
        $httpBackend = _$httpBackend_;
        scope = _$rootScope_;
    }));

    // makes sure all expected requests are made by the time the test ends
    afterEach(function() {
      $httpBackend.verifyNoOutstandingExpectation();
      $httpBackend.verifyNoOutstandingRequest();
    });

    describe('should load data successfully', function() {

        beforeEach(function() {
           $httpBackend.expectGET('/api/1').response(expectedResponse);
           $controller('SimpleController', { $scope: scope });

           // causes the http requests which will be issued by myService to be completed synchronously, and thus will process the fake response we defined above with the expectGET
           $httpBackend.flush();
        });

        it('using loadData()', function() {
          scope.loadData();
          expect(scope.data).toEqual(expectedResponse);
        });

        it('using loadData2()', function () {
          scope.loadData2();
          expect(scope.data).toEqual(expectedResponse);
        });
    });

    describe('should fail to load data', function() {
        beforeEach(function() {
           $httpBackend.expectGET('/api/1').response(500); // return 500 - Server Error
           $controller('SimpleController', { $scope: scope });
           $httpBackend.flush();
        });

        it('using loadData()', function() {
          scope.loadData();
          expect(scope.error).toEqual('ERROR');
        });

        it('using loadData2()', function () {
          scope.loadData2();
          expect(scope.error).toEqual('ERROR');
        });
    });           
});
2-返回一个完全被嘲笑的承诺 如果您正在测试的东西具有复杂的依赖关系,并且所有的设置都是令人头痛的,那么您可能仍然希望在尝试时模拟服务和调用本身。不同的是,你会想要完全模仿承诺。这样做的缺点是可以创建所有可能的模拟承诺,但是您可以通过创建自己的函数来创建这些对象,从而使这变得更容易

之所以这样做,是因为我们假装它通过调用
success
error
提供的处理程序来解决问题,然后立即执行
,从而使它同步完成

'use strict';

describe('SimpleControllerTests', function () {

    var scope;
    var expectedResponse = { name: 'this is a mocked response' };
    var $controller, _mockMyService, _mockPromise = null;

    beforeEach(module('myApp'));

    beforeEach(inject(function(_$rootScope_, _$controller_){ 
        $controller = _$controller_;
        scope = _$rootScope_;

        _mockMyService = {
            get: function() {
               return _mockPromise;
            }
        };
    }));

    describe('should load data successfully', function() {

        beforeEach(function() {

          _mockPromise = {
             then: function(successFn) {
               successFn(expectedResponse);
             },
             success: function(fn) {
               fn(expectedResponse);
             }
          };

           $controller('SimpleController', { $scope: scope, myService: _mockMyService });
        });

        it('using loadData()', function() {
          scope.loadData();
          expect(scope.data).toEqual(expectedResponse);
        });

        it('using loadData2()', function () {
          scope.loadData2();
          expect(scope.data).toEqual(expectedResponse);
        });
    });

    describe('should fail to load data', function() {
        beforeEach(function() {
          _mockPromise = {
            then: function(successFn, errorFn) {
              errorFn();
            },
            error: function(fn) {
              fn();
            }
          };

          $controller('SimpleController', { $scope: scope, myService: _mockMyService });
        });

        it('using loadData()', function() {
          scope.loadData();
          expect(scope.error).toEqual("ERROR");
        });

        it('using loadData2()', function () {
          scope.loadData2();
          expect(scope.error).toEqual("ERROR");
        });
    });           
});
我很少选择选项2,即使在大型应用程序中也是如此

值得一提的是,您的
loadData
loadData2
http处理程序有一个错误。它们引用
response.data
,但是将直接使用解析的响应数据调用,而不是响应对象(因此它应该是
data
,而不是
response.data
)。

不要混淆关注点! 在控制器中使用
$httpBackend
是一个坏主意,因为您在测试中混用了各种问题。是否从端点检索数据不是控制器的问题,而是您正在调用的数据服务的问题

如果更改服务内的端点Url,那么您将不得不修改两个测试:服务测试和控制器测试,您可以更清楚地看到这一点

同样如前所述,
success
error
的使用是语法上的甜点,我们应该坚持使用
then
catch
。但实际上,您可能会发现自己需要测试“遗留”代码。因此,我使用这个函数:

function generatePromiseMock(resolve, reject) {
    var promise;
    if(resolve) {
        promise = q.when({data: resolve});
    } else if (reject){
        promise = q.reject({data: reject});
    } else {
        throw new Error('You need to provide an argument');
    }
    promise.success = function(fn){
        return q.when(fn(resolve));
    };
    promise.error = function(fn) {
        return q.when(fn(reject));
    };
    return promise;
}
通过调用此函数,您将获得一个真正的承诺,在需要时响应
然后
捕获
方法,并将为
成功
错误
回调工作。请注意,成功和错误本身返回一个承诺,因此它将使用链式
然后
方法


(注意:在第4行和第6行,函数在对象的数据属性中返回resolve和reject值。这是为了模拟$http的行为,因为它返回数据、http状态等。)是的,不要在控制器中使用$httpbackend,因为我们不需要发出真正的请求,您只需要确保一个单元完全按照预期工作,看看这个简单的控制器测试,它很容易理解

/**
 * @description Tests for adminEmployeeCtrl controller
 */
(function () {

    "use strict";

    describe('Controller: adminEmployeeCtrl ', function () {

        /* jshint -W109 */
        var $q, $scope, $controller;
        var empService;
        var errorResponse = 'Not found';


        var employeesResponse = [
            {id:1,name:'mohammed' },
            {id:2,name:'ramadan' }
        ];

        beforeEach(module(
            'loadRequiredModules'
        ));

        beforeEach(inject(function (_$q_,
                                    _$controller_,
                                    _$rootScope_,
                                    _empService_) {
            $q = _$q_;
            $controller = _$controller_;
            $scope = _$rootScope_.$new();
            empService = _empService_;
        }));

        function successSpies(){

            spyOn(empService, 'findEmployee').and.callFake(function () {
                var deferred = $q.defer();
                deferred.resolve(employeesResponse);
                return deferred.promise;
                // shortcut can be one line
                // return $q.resolve(employeesResponse);
            });
        }

        function rejectedSpies(){
            spyOn(empService, 'findEmployee').and.callFake(function () {
                var deferred = $q.defer();
                deferred.reject(errorResponse);
                return deferred.promise;
                // shortcut can be one line
                // return $q.reject(errorResponse);
            });
        }

        function initController(){

            $controller('adminEmployeeCtrl', {
                $scope: $scope,
                empService: empService
            });
        }


        describe('Success controller initialization', function(){

            beforeEach(function(){

                successSpies();
                initController();
            });

            it('should findData by calling findEmployee',function(){
                $scope.findData();
                // calling $apply to resolve deferred promises we made in the spies
                $scope.$apply();
                expect($scope.loadingEmployee).toEqual(false);
                expect($scope.allEmployees).toEqual(employeesResponse);
            });
        });

        describe('handle controller initialization errors', function(){

            beforeEach(function(){

                rejectedSpies();
                initController();
            });

            it('should handle error when calling findEmployee', function(){
                $scope.findData();
                $scope.$apply();
                // your error expectations
            });
        });
    });
}());

对于角度模拟(您已经在使用),可以使用
$httpBackend
设置http调用的期望值并指定它们应该如何响应,然后可以
刷新它们以同步完成测试。为什么不走那条路呢?看:谢谢,但我看不出这有什么帮助。在本例中,我尝试对控制器进行单元测试,因此我完全模拟了进行$http调用的服务。在本例中,当模拟服务被注入控制器时,没有调用$http的代码。感谢您教我将http功能从控制器中分离出来,现在将其放在控制器中是没有意义的,因为我已经看到了这一点。另外,在Jasmine 2.3.1中,您需要在spy上使用
和.returnValue
,我将如何测试错误条件,而不是
andReturn
感谢您的回答?我开始认为在控制器之外显式地使用成功和错误确实是个坏主意。我想控制员公司