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

尼克松

我正在尝试找出在控制器中进行单元测试成功和错误回调的最佳方法。只要控制器仅使用默认的$ 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:对象不支持属性或方法'成功'”。在这种情况下,我得到的是getDeferred.promise没有成功函数。好了,这是一个问题,编写此测试的一种好方法是什么,以便我可以测试模拟服务的“成功”,“错误”和“然后”条件?

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

编辑

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

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()来测试错误\成功条件:

'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-返回完全模拟的承诺

如果您要测试的东西具有复杂的依赖关系,并且所有设置都令人头疼,那么您可能仍想像尝试的那样模拟服务和调用本身。所不同的是,您将要完全嘲笑诺言。不利的一面是创建所有可能的模拟承诺,但是您可以通过创建自己的函数来创建这些对象来简化这一过程。

这部作品的原因是因为我们假装它解决了通过调用所提供的处理程序successerrorthen立即使其同步完成。

'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。

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

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

使用Laravel 5 [phpunit]的测试方法和控制器

使用xUnit测试控制器

使用PageableDefault测试Rest控制器

使用Guardian进行控制器测试

使用VisualStudioOnline测试控制器设置

Angular测试使用RouteParams的控制器

测试使用@CurrentTenant的Grails控制器

尝试使用UrlHelper测试控制器

使用嵌套资源测试控制器

使用Mockery的Laravel控制器测试

如何使用Jasmine和NodeJs在回调函数中测试逻辑以测试Mongoose控制器

使用angular-text-patterns使用$ resource和$ promise测试角度控制器

使用嵌套资源和私有方法使用rspec测试Rails控制器

使用 XUnit 和 AspNetCore.TestHost 测试 .net core web api 控制器文件上传

如何使用 spring boot 和 mockito 对控制器方法进行单元测试

如何测试休息控制器,静态util的使用JUnit和调用的Mockito

使用Karma和Jasmine在Angular JS中测试父控制器范围的变量

使用Node.js,Gulp.js和Mocha单元测试angularjs控制器

使用Spring Boot,Kotlin和Junit进行休息控制器单元测试

Angularjs和Jasmine:使用进行Ajax调用的服务测试控制器

如何使用 Mocha、Chai 和 Sinon 正确测试 Express 控制器方法

如何使用jUnit在JSF项目中测试控制器和模型?

在使用PlaySpecification编写的控制器测试之前和之后,如何执行一组代码?

使用 xunit 和 moq 进行 net core api 控制器单元测试

使用Moq和Mock设置的具有IConfiguration的单元测试控制器返回null

如何模拟服务和测试后的控制器方法

如何分别测试ExpressJS路由和“控制器”

Factory Girl和Rspec控制器测试失败

使用业力测试不使用$ scope的控制器