测试成功()和错误的控制器() [英] Test a controller with success() and error ()

查看:285
本文介绍了测试成功()和错误的控制器()的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图找出单元测试成功和错误回调在控制器的最佳途径。我能够模拟出服务方法,只要控制器仅使用默认$ Q功能,比如'然后'(见下面的例子)。当控制器响应一个'成功'或'错误'的诺言,我有一个问题。 (很抱歉,如果我的术语是不正确的)。

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

  VAR myControllers = angular.module('myControllers');myControllers.controller('SimpleController',['$范围,为myService,
  功能($范围,为myService){      变种的id = 1;
      $ scope.loadData =功能(){
          myService.get(ID)。然后(功能(响应){
              $ scope.data = response.data;
          });
      };      $ scope.loadData2 =功能(){
          myService.get(ID).success(功能(响应){
              $ scope.data = response.data;
          })错误(功能(响应){
              $ scope.error ='错误';
          });
      };
  }]);
cocoApp.service('为myService',[
    $ HTTP',函数($ HTTP){
        功能GET(ID){
            返回$ http.get('/ API /'+ id)的;
        }
    }
]);

我有以下测试

 使用严格的;描述(SimpleControllerTests',函数(){    VAR范围;
    VAR控制器;
    VAR GETRESPONSE = {数据:这是一个嘲弄响应'};    beforeEach(angular.mock.module('对myApp'));    beforeEach(angular.mock.inject(函数($ Q $控制器,$ rootScope,$ routeParams){        范围= $ rootScope;
        VAR myServiceMock = {
            得到:函数(){}
        };        //设置为得到一个承诺
        变种getDeferred = $ q.defer();
        getDeferred.resolve(GETRESPONSE);
        spyOn(myServiceMock,'得到')andReturn(getDeferred.promise)。        控制器= $控制器('SimpleController',{$适用范围:适用范围,为myService:myServiceMock});
    }));
    它('这个测试工程',函数(){
        scope.loadData();
        期待(scope.data).toEqual(getResponse.data);
    });    它('这行不通',函数(){
        scope.loadData2();
        期待(scope.data).toEqual(getResponse.data);
    });
});

第一个测试通过,第二失败,错误类型错误:对象不支持属性或方法'成功'。我得到这个实例getDeferred.promise
不具有成功的功能。好了这里的问题,什么是写这个测试,这样我可以测试'成功','错误'和一个不错的方式; '然后'一个嘲笑服务条件?

我开始觉得我应该避免在我的控制器使用成功()和误差()的...

修改

所以想着这一些,并且由于下面的详细解答后,我得出的结论是,在控制器的处理成功和错误回调是坏的。作为HackedByChinese提到下面成功\\的错误是由$ HTTP增加语法糖。因此,在实际事实上,试图处理成功\\的错误我又让$ HTTP的关注渗入到我的控制器,而这正是我试图避免通过包装在一个服务的$ HTTP调用。我要采取的方法是改变控制器不使用成功\\错误:

  myControllers.controller('SimpleController',['$范围,为myService,
  功能($范围,为myService){      变种的id = 1;
      $ scope.loadData =功能(){
          myService.get(ID)。然后(功能(响应){
              $ scope.data = response.data;
          },函数(响应){
              $ scope.error ='错误';
          });
      };
  }]);

这样我可以测试通过调用决心(错误\\成功的条件),并拒绝()确认递延对象:

 使用严格的;描述(SimpleControllerTests',函数(){    VAR范围;
    VAR控制器;
    VAR GETRESPONSE = {数据:这是一个嘲弄响应'};
    VAR getDeferred;
    VAR myServiceMock;    //模拟应用,使我们能够注入我们自己的依赖
    beforeEach(angular.mock.module('对myApp'));
    //模拟出于同样的原因,控制器和包括$ rootScope和$控制器
    beforeEach(angular.mock.inject(函数($ Q $控制器,$ rootScope,$ routeParams){        范围= $ rootScope;
        myServiceMock = {
            得到:函数(){}
        };
        //设置为得到一个承诺
        getDeferred = $ q.defer();
        spyOn(myServiceMock,'得到')andReturn(getDeferred.promise)。
        控制器= $控制器('SimpleController',{$适用范围:适用范围,为myService:myServiceMock});
    }));    它('应该设置一些数据上成功时的范围',函数(){
        getDeferred.resolve(GETRESPONSE);
        scope.loadData();
        。范围$适用();
        期待(myServiceMock.get).toHaveBeenCalled();
        期待(scope.data).toEqual(getResponse.data);
    });    它('应该做别的事情的时候不成功',函数(){
        getDeferred.reject(GETRESPONSE);
        scope.loadData();
        。范围$适用();
        期待(myServiceMock.get).toHaveBeenCalled();
        期待(scope.error).to​​Equal(错误);
    });
});


解决方案

正如有人在一个已删除的答案已经提到的,成功错误的语法糖由 $ HTTP 添加,因此他们是当你创建自己的承诺,不存在。你有两个选择:

1 - 不要嘲笑的服务,并使用 $ httpBackend 设置的期望和刷新

我们的想法是让你的为myService 像通常那样不知道它正在测试中。 $ httpBackend 会让你建立的预期和反应,并冲洗它们,以便您可以同步完成测试。 $ HTTP 将不会有任何更聪明,它会返回外观和功能就像一个实际的承诺。此选项是好的,如果你有几个HTTP期望简单的测试。

 使用严格的;描述(SimpleControllerTests',函数(){    VAR范围;
    VAR expectedResponse = {名称:'这是一个嘲弄响应'};
    变量$ httpBackend,$控制器;    beforeEach(模块('对myApp'));    beforeEach(注(功能(_ $ rootScope_,_ $ controller_,_ $ _ httpBackend){
        //下划线是一个约定NG了解到,刚刚帮助我们从变量参数区分
        $控制器= _ $ controller_;
        $ httpBackend = _ $ httpBackend_;
        范围= _ $ rootScope_;
    }));    //确保所有预期请求都由时所作的测试结束
    afterEach(函数(){
      $ httpBackend.verifyNoOutstandingExpectation();
      $ httpBackend.verifyNoOutstandingRequest();
    });    描述(应该成功加载数据',函数(){        beforeEach(函数(){
           。$ httpBackend.expectGET('/ API / 1)响应(expectedResponse);
           $控制器('SimpleController',{$范围:适用范围});           //导致将由为myService发出的HTTP请求中同步完成,从而将处理上面我们使用的expectGET定义的假响应
           $ httpBackend.flush();
        });        它('使用loadData()',函数(){
          scope.loadData();
          期待(scope.data).toEqual(expectedResponse);
        });        它('使用loadData2()',函数(){
          scope.loadData2();
          期待(scope.data).toEqual(expectedResponse);
        });
    });    描述(应该无法加载数据',函数(){
        beforeEach(函数(){
           。$ httpBackend.expectGET('/ API / 1)响应(500); //返回500 - 服务器错误
           $控制器('SimpleController',{$范围:适用范围});
           $ httpBackend.flush();
        });        它('使用loadData()',函数(){
          scope.loadData();
          期待(scope.error).to​​Equal(错误);
        });        它('使用loadData2()',函数(){
          scope.loadData2();
          期待(scope.error).to​​Equal(错误);
        });
    });
});

2 - 返回一个完全嘲笑诺

如果您正在测试有复杂的依赖关系,并全部设置了的事情很头疼,你可能仍然需要模拟的服务和呼叫自己,你已经尝试。不同的是,你要完全模拟的承诺。这种方法的缺点可以创造一切可能的模拟承诺,但是你可以做的更容易创建自己的功能,用于创建这些对象。

这部作品的原因是因为我们pretend,它解决了通过调用成功错误然后立即使其同步完成。

 使用严格的;描述(SimpleControllerTests',函数(){    VAR范围;
    VAR expectedResponse = {名称:'这是一个嘲弄响应'};
    变量$控制器,_mockMyService,_mockPromise = NULL;    beforeEach(模块('对myApp'));    beforeEach(注(功能(_ $ rootScope_,_ $ _控制器){
        $控制器= _ $ controller_;
        范围= _ $ rootScope_;        _mockMyService = {
            得到:函数(){
               返回_mockPromise;
            }
        };
    }));    描述(应该成功加载数据',函数(){        beforeEach(函数(){          _mockPromise = {
             那么:功能(successFn){
               successFn(expectedResponse);
             },
             成功:函数(FN){
               FN(expectedResponse);
             }
          };           $控制器('SimpleController',{$适用范围:适用范围,为myService:_mockMyService});
        });        它('使用loadData()',函数(){
          scope.loadData();
          期待(scope.data).toEqual(expectedResponse);
        });        它('使用loadData2()',函数(){
          scope.loadData2();
          期待(scope.data).toEqual(expectedResponse);
        });
    });    描述(应该无法加载数据',函数(){
        beforeEach(函数(){
          _mockPromise = {
            那么:功能(successFn,errorFn){
              errorFn();
            },
            错误:功能(FN){
              FN();
            }
          };          $控制器('SimpleController',{$适用范围:适用范围,为myService:_mockMyService});
        });        它('使用loadData()',函数(){
          scope.loadData();
          期待(scope.error).to​​Equal(ERROR);
        });        它('使用loadData2()',函数(){
          scope.loadData2();
          期待(scope.error).to​​Equal(ERROR);
        });
    });
});

我很少去选择2,即使是在大​​的应用程序。

有关它的价值,你的 loadData loadData2 HTTP处理程序有一个错误。他们引用 response.data 处理将直接与解析响应数据,而不是响应对象被调用(所以它应该是数据而不是 response.data )。

I'm trying to work out the best way to unit test success and error callbacks in controllers. I am able to mock out service methods, as long as the controller only uses the default $q functions such as 'then' (see the example below). I'm having an issue when the controller responds to a 'success' or 'error' promise. (Sorry if my terminology is not correct).

Here is an example controller \ service

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

I have the following test

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

The first test passes and the second fails with the error "TypeError: Object doesn't support property or method 'success'". I get that in this instance that getDeferred.promise does not have a success function. Okay here is the question, what is a nice way to write this test so that I can test the 'success', 'error' & 'then' conditions of a mocked service ?

I'm starting to think that I should avoid the use of success() and error() in my controllers...

EDIT

So after thinking about this some more, and thanks to the detailed answer below, I've come to the conclusion that the handling the success and error callbacks in the controller is bad. As HackedByChinese mentions below success\error is syntactic sugar that is added by $http. So, in actual fact, by trying to handle success \ error I am letting $http concerns leak into my controller, which is exactly what I was trying to avoid by wrapping the $http calls in a service. The approach I'm going to take is to change the controller not to use 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';
          });
      };
  }]);

This way I can test the error \ success conditions by calling resolve() and reject() on the deferred object:

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

解决方案

As someone had mentioned in a deleted answer, success and error are syntactic sugar added by $http so they aren't there when you create your own promise. You have two options:

1 - Don't mock the service and use $httpBackend to setup expectations and flush

The idea is to let your myService act like it normally would without knowing it's being tested. $httpBackend will let you set up expectations and responses, and flush them so you can complete your tests synchronously. $http won't be any wiser and the promise it returns will look and function like a real one. This option is good if you have simple tests with few HTTP expectations.

'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 - Return a fully-mocked promise

If the thing you're testing has complicated dependencies and all the set-up is a headache, you may still want to mock the services and the calls themselves as you have attempted. The difference is that you'll want to fully mock promise. The downside of this can be creating all the possible mock promises, however you could make that easier by creating your own function for creating these objects.

The reason this works is because we pretend that it resolves by invoking the handlers provided by success, error, or then immediately, causing it to complete synchronously.

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

I rarely go for option 2, even in big applications.

For what it's worth, your loadData and loadData2 http handlers have an error. They reference response.data but the handlers will be called with the parsed response data directly, not the response object (so it should be data instead of response.data).

这篇关于测试成功()和错误的控制器()的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆