source

컨트롤러를 성공() 및 오류()로 테스트합니다.

factcode 2023. 10. 1. 22:01
반응형

컨트롤러를 성공() 및 오류()로 테스트합니다.

컨트롤러의 테스트 성공 및 오류 콜백을 단위화하는 최선의 방법을 찾고 있습니다.컨트롤러가 '그때'와 같은 기본 $q 함수만 사용하는 한 서비스 방법을 무시할 수 있습니다(아래 예 참조).컨트롤러가 '성공' 또는 '오류' 약속에 응답할 때 문제가 발생합니다.(내 용어가 정확하지 않다면 죄송합니다.)

여기 컨트롤러 \서비스 예시가 있습니다.

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: Object가 속성 또는 메서드 'success'를 지원하지 않습니다"라는 오류가 표시됩니다.이 경우 getDerferred.promise는 성공 함수가 없다는 것을 알 수 있습니다.여기 질문이 있습니다. 조롱당한 서비스의 '성공', '오류' 및 '그때' 조건을 테스트할 수 있도록 이 테스트를 작성하는 좋은 방법은 무엇입니까?

컨트롤러에 성공()과 오류()를 사용하지 말아야겠다는 생각이 들기 시작했습니다.

편집

그래서 이에 대해 좀 더 고민한 결과 아래의 상세한 답변 덕분에 컨트롤러에서 성공오류 콜백을 처리하는 것이 나쁘다는 결론에 도달했습니다.HackedByChinese가 아래에 언급한 성공\error는 $http만큼 추가된 통사적 설탕입니다.그래서 실제로는 성공 \ 오류를 처리하려고 노력함으로써 $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()를 호출하여 오류 \성공 조건을 테스트할 수 있습니다.

'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, 아니면then즉시, 동기적으로 완료되도록 합니다.

'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다.합니다를 합니다.response.data그러나 응답 개체가 아닌 구문 분석된 응답 데이터로 핸들러가 직접 호출됩니다(따라서 다음과 같이 해야 합니다).dataresponse.data).

걱정하지 마!

으로.$httpBackend컨트롤러 내부는 테스트 내부의 우려를 혼합하고 있으므로 좋지 않은 아이디어입니다.Endpoint에서 데이터를 검색하는지 여부는 컨트롤러의 관심 사항이 아니며, 호출 중인 데이터 서비스의 관심 사항입니다.

서비스 내부의 Endpoint 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;
}

하면 에 수 .then그리고.catch할 때 .success아니면error콜백 성공과 성공과 오류는 약속 자체를 반환하므로 체인으로 작동합니다에서 작동합니다.then방법들.

(참고: 4행과 6행에서 함수는 개체의 데이터 속성 내부의 확인 값과 거부 값을 반환합니다.데이터를 반환하므로 $http의 Behavior of $http, http Status 등을 조롱합니다.)

예, 컨트롤러에 $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
            });
        });
    });
}());

언급URL : https://stackoverflow.com/questions/23731637/test-a-controller-with-success-and-error

반응형