programing

Angular를 테스트하려면 어떻게 해야 합니까?재스민과의 JS 서비스?

codeshow 2023. 3. 28. 22:22
반응형

Angular를 테스트하려면 어떻게 해야 합니까?재스민과의 JS 서비스?

(관련 질문이 있습니다.재스민 테스트에서는 각도가 표시되지 않습니다.JS 모듈)

Angular를 부트스트랩하지 않고 서비스를 테스트하고 싶을 뿐입니다.

몇 가지 예시와 튜토리얼은 봤지만 난 아무데도 안 가.

파일은 3개뿐입니다.

  • myService.js: 여기서 Angular는JS 서비스

  • test_myService.js: 서비스 재스민 테스트를 정의합니다.

  • specRunner.html: 일반적인 재스민 설정을 가진 HTML 파일로 앞의 2개의 파일과 Jasmine, Angularjs 및 angular-mocks.js 파일을 Import합니다.

이것은 서비스 코드입니다(테스트하지 않을 때는 예상대로 동작합니다).

var myModule = angular.module('myModule', []);

myModule.factory('myService', function(){

    var serviceImplementation   = {};
    serviceImplementation.one   = 1;
    serviceImplementation.two   = 2;
    serviceImplementation.three = 3;

    return serviceImplementation

});

서비스를 단독으로 테스트하려고 하기 때문에 접속하여 방법을 확인할 수 있을 것입니다.질문입니다. 어떻게 하면 AngularJs를 부트스트래핑하지 않고 테스트에 서비스를 삽입할 수 있을까요?

예를 들어, Jasmine을 사용하여 서비스 메서드에서 반환되는 값을 테스트하려면 어떻게 해야 합니까?

describe('myService test', function(){
    describe('when I call myService.one', function(){
        it('returns 1', function(){
            myModule = angular.module('myModule');
                    //something is missing here..
            expect( myService.one ).toEqual(1);
        })

    })

});

문제는 위의 예에서는 서비스를 인스턴스화하는 공장 메서드가 호출되지 않는다는 것입니다(모듈을 작성하는 것만으로는 서비스가 인스턴스화되지 않습니다).

서비스를 각도로 인스턴스화하기 위해.인젝터는 서비스가 정의되어 있는 모듈과 함께 호출해야 합니다.그런 다음 서비스에 대한 새로운 인젝터 개체에 요청할 수 있으며 서비스가 최종적으로 인스턴스화되었을 때만 해당됩니다.

다음과 같은 기능이 있습니다.

describe('myService test', function(){
    describe('when I call myService.one', function(){
        it('returns 1', function(){
            var $injector = angular.injector([ 'myModule' ]);
            var myService = $injector.get( 'myService' );
            expect( myService.one ).toEqual(1);
        })

    })

});

또 다른 방법은 'incoke'를 사용하여 서비스를 함수에 전달하는 것입니다.

describe('myService test', function(){
    describe('when I call myService.one', function(){
        it('returns 1', function(){

            myTestFunction = function(aService){
                expect( aService.one ).toEqual(1);
            }

            //we only need the following line if the name of the 
            //parameter in myTestFunction is not 'myService' or if
            //the code is going to be minify.
            myTestFunction.$inject = [ 'myService' ];

            var myInjector = angular.injector([ 'myModule' ]);
            myInjector.invoke( myTestFunction );
        })

    })

});

그리고 마지막으로, '적절한' 방법은 '각' 재스민 블록에 '주입'과 '모듈'을 사용하는 것이다.이를 수행할 때 'inject' 기능은 표준 angularjs 패키지가 아니라 ngMock 모듈에 있으며 재스민에서만 작동한다는 것을 알아야 합니다.

describe('myService test', function(){
    describe('when I call myService.one', function(){
        beforeEach(module('myModule'));
        it('returns 1', inject(function(myService){ //parameter name = service name

            expect( myService.one ).toEqual(1);

        }))

    })

});

위의 답변은 아마 정상적으로 동작하고 있을 것입니다(아직 시험해 본 적이 없습니다). 그러나 저는 종종 더 많은 테스트를 수행해야 하기 때문에 테스트 자체에 주입하지 않습니다.케이스()를 descript 블록으로 그룹화하고 각 descript 블록의 beforeEach() 또는 beforeAll()에서 주입을 실행합니다.

또한 Robert는 테스트에서 서비스 또는 공장에 대해 인식시키기 위해 Angular $injector를 사용해야 한다고 말합니다.Angular는 응용 프로그램에서도 이 인젝터 자체를 사용하여 응용 프로그램에 사용 가능한 정보를 알려줍니다.단, 여러 장소에서 호출할 수 있으며 명시적으로 호출하지 않고 암묵적으로 호출할 수도 있습니다.아래의 스펙테스트 파일 예에서 알 수 있듯이, beforeEach() 블록은 암시적으로 인젝터를 호출하여 테스트 내에서 할당할 수 있도록 합니다.

예를 들어, 그룹화와 이전 블록 사용으로 돌아가 보겠습니다.Cat 서비스를 만들고 있는데 테스트하고 싶기 때문에 서비스를 작성하고 테스트하기 위한 간단한 설정은 다음과 같습니다.

app.module

var catsApp = angular.module('catsApp', ['ngMockE2E']);

angular.module('catsApp.mocks', [])
.value('StaticCatsData', function() {
  return [{
    id: 1,
    title: "Commando",
    name: "Kitty MeowMeow",
    score: 123
  }, {
    id: 2,
    title: "Raw Deal",
    name: "Basketpaws",
    score: 17
  }, {
    id: 3,
    title: "Predator",
    name: "Noseboops",
    score: 184
  }];
});

catsApp.factory('LoggingService', ['$log', function($log) {

  // Private Helper: Object or String or what passed
    // for logging? Let's make it String-readable...
  function _parseStuffIntoMessage(stuff) {
    var message = "";
    if (typeof stuff !== "string") {
      message = JSON.stringify(stuff)
    } else {
      message = stuff;
    }

    return message;
  }

  /**
   * @summary
   * Write a log statement for debug or informational purposes.
   */
  var write = function(stuff) {
    var log_msg = _parseStuffIntoMessage(stuff);
    $log.log(log_msg);
  }

  /**
   * @summary
   * Write's an error out to the console.
   */
  var error = function(stuff) {
    var err_msg = _parseStuffIntoMessage(stuff);
    $log.error(err_msg);
  }

  return {
    error: error,
    write: write
  };

}])

catsApp.factory('CatsService', ['$http', 'LoggingService', function($http, Logging) {

  /*
    response:
      data, status, headers, config, statusText
  */
  var Success_Callback = function(response) {
    Logging.write("CatsService::getAllCats()::Success!");
    return {"status": status, "data": data};
  }

  var Error_Callback = function(response) {
    Logging.error("CatsService::getAllCats()::Error!");
    return {"status": status, "data": data};
  }

  var allCats = function() {
    console.log('# Cats.allCats()');
    return $http.get('/cats')
      .then(Success_Callback, Error_Callback);
  }

  return {
    getAllCats: allCats
  };

}]);

var CatsController = function(Cats, $scope) {

  var vm = this;

  vm.cats = [];

  // ========================

  /**
   * @summary
   * Initializes the controller.
   */
  vm.activate = function() {
    console.log('* CatsCtrl.activate()!');

    // Get ALL the cats!
    Cats.getAllCats().then(
      function(litter) {
        console.log('> ', litter);
        vm.cats = litter;
        console.log('>>> ', vm.cats);
      }  
    );
  }

  vm.activate();

}
CatsController.$inject = ['CatsService', '$scope'];
catsApp.controller('CatsCtrl', CatsController);

사양: 고양이 컨트롤러

'use strict';

describe('Unit Tests: Cats Controller', function() {

    var $scope, $q, deferred, $controller, $rootScope, catsCtrl, mockCatsData, createCatsCtrl;

    beforeEach(module('catsApp'));
    beforeEach(module('catsApp.mocks'));

    var catsServiceMock;

    beforeEach(inject(function(_$q_, _$controller_, $injector, StaticCatsData) {
      $q = _$q_;
      $controller = _$controller_;

      deferred = $q.defer();

      mockCatsData = StaticCatsData();

      // ToDo:
        // Put catsServiceMock inside of module "catsApp.mocks" ?
      catsServiceMock = {
        getAllCats: function() {
          // Just give back the data we expect.
          deferred.resolve(mockCatsData);
          // Mock the Promise, too, so it can run
            // and call .then() as expected
          return deferred.promise;
        }
      };
    }));


    // Controller MOCK
    var createCatsController;
    // beforeEach(inject(function (_$rootScope_, $controller, FakeCatsService) {
    beforeEach(inject(function (_$rootScope_, $controller, CatsService) {

      $rootScope = _$rootScope_;

      $scope = $rootScope.$new();
      createCatsController = function() {
          return $controller('CatsCtrl', {
              '$scope': $scope,
              CatsService: catsServiceMock
          });    
      };
    }));

    // ==========================

    it('should have NO cats loaded at first', function() {
      catsCtrl = createCatsController();

      expect(catsCtrl.cats).toBeDefined();
      expect(catsCtrl.cats.length).toEqual(0);
    });

    it('should call "activate()" on load, but only once', function() {
      catsCtrl = createCatsController();
      spyOn(catsCtrl, 'activate').and.returnValue(mockCatsData);

      // *** For some reason, Auto-Executing init functions
      // aren't working for me in Plunkr?
      // I have to call it once manually instead of relying on
      // $scope creation to do it... Sorry, not sure why.
      catsCtrl.activate();
      $rootScope.$digest();   // ELSE ...then() does NOT resolve.

      expect(catsCtrl.activate).toBeDefined();
      expect(catsCtrl.activate).toHaveBeenCalled();
      expect(catsCtrl.activate.calls.count()).toEqual(1);

      // Test/Expect additional  conditions for 
        // "Yes, the controller was activated right!"
      // (A) - there is be cats
      expect(catsCtrl.cats.length).toBeGreaterThan(0);
    });

    // (B) - there is be cats SUCH THAT
      // can haz these properties...
    it('each cat will have a NAME, TITLE and SCORE', function() {
      catsCtrl = createCatsController();
      spyOn(catsCtrl, 'activate').and.returnValue(mockCatsData);

      // *** and again...
      catsCtrl.activate();
      $rootScope.$digest();   // ELSE ...then() does NOT resolve.

      var names = _.map(catsCtrl.cats, function(cat) { return cat.name; })
      var titles = _.map(catsCtrl.cats, function(cat) { return cat.title; })
      var scores = _.map(catsCtrl.cats, function(cat) { return cat.score; })

      expect(names.length).toEqual(3);
      expect(titles.length).toEqual(3);
      expect(scores.length).toEqual(3); 
    });

});

사양: 캣츠

'use strict';

describe('Unit Tests: Cats Service', function() {

  var $scope, $rootScope, $log, cats, logging, $httpBackend, mockCatsData;

  beforeEach(module('catsApp'));
  beforeEach(module('catsApp.mocks'));

  describe('has a method: getAllCats() that', function() {

    beforeEach(inject(function($q, _$rootScope_, _$httpBackend_, _$log_, $injector, StaticCatsData) {
      cats = $injector.get('CatsService');
      $rootScope = _$rootScope_;
      $httpBackend = _$httpBackend_;

      // We don't want to test the resolving of *actual data*
      // in a unit test.
      // The "proper" place for that is in Integration Test, which
      // is basically a unit test that is less mocked - you test
      // the endpoints and responses and APIs instead of the
      // specific service behaviors.
      mockCatsData = StaticCatsData();

      // For handling Promises and deferrals in our Service calls...
      var deferred = $q.defer();
      deferred.resolve(mockCatsData); //  always resolved, you can do it from your spec

      // jasmine 2.0
        // Spy + Promise Mocking
        // spyOn(obj, 'method'), (assumes obj.method is a function)
      spyOn(cats, 'getAllCats').and.returnValue(deferred.promise);

      /*
        To mock $http as a dependency, use $httpBackend to
        setup HTTP calls and expectations.
      */
      $httpBackend.whenGET('/cats').respond(200, mockCatsData);
    }));

    afterEach(function() {
      $httpBackend.verifyNoOutstandingExpectation();
      $httpBackend.verifyNoOutstandingRequest();
    })

    it(' exists/is defined', function() {
      expect( cats.getAllCats ).toBeDefined();
      expect( typeof cats.getAllCats ).toEqual("function");
    });

    it(' returns an array of Cats, where each cat has a NAME, TITLE and SCORE', function() {
      cats.getAllCats().then(function(data) {
        var names = _.map(data, function(cat) { return cat.name; })
        var titles = _.map(data, function(cat) { return cat.title; })
        var scores = _.map(data, function(cat) { return cat.score; })

        expect(names.length).toEqual(3);
        expect(titles.length).toEqual(3);
        expect(scores.length).toEqual(3);
      })
    });

  })

  describe('has a method: getAllCats() that also logs', function() {

      var cats, $log, logging;

      beforeEach(inject(
        function(_$log_, $injector) {
          cats = $injector.get('CatsService');
          $log = _$log_;
          logging = $injector.get('LoggingService');

          spyOn(cats, 'getAllCats').and.callThrough();
        }
      ))

      it('that on SUCCESS, $logs to the console a success message', function() {
        cats.getAllCats().then(function(data) {
          expect(logging.write).toHaveBeenCalled();
          expect( $log.log.logs ).toContain(["CatsService::getAllCats()::Success!"]);
        })
      });

    })

});

편집 코멘트를 바탕으로 답변을 조금 더 복잡하게 업데이트했습니다.또, 유닛 테스트의 데먼스트레이션도 작성했습니다.구체적으로는 "컨트롤러 서비스 자체에 $log와 같은 단순한 의존관계가 있다면?"이라는 코멘트가 있었습니다.이것은 테스트 케이스의 예에 포함되어 있습니다.도움이 됐으면 좋겠다!지구를 테스트하거나 해킹하세요!!!

https://embed.plnkr.co/aSPHnr/

Google Places Autocomplete라는 또 다른 디렉티브가 필요한 디렉티브를 테스트해야 했습니다.그냥 비웃어야 할지...어쨌든 이것은 gPlaces를 필요로 하는 명령에 대한 오류를 버리는 것으로 작동했습니다.자동 완성.

describe('Test directives:', function() {
    beforeEach(module(...));
    beforeEach(module(...));
    beforeEach(function() {
        angular.module('google.places', [])
        .directive('gPlacesAutocomplete',function() {
            return {
                require: ['ngModel'],
                restrict: 'A',
                scope:{},
                controller: function() { return {}; }
             };
        });
     });
     beforeEach(module('google.places'));
});

컨트롤러를 테스트하고 싶다면 아래와 같이 주입하여 테스트할 수 있습니다.

describe('When access Controller', function () {
    beforeEach(module('app'));

    var $controller;

    beforeEach(inject(function (_$controller_) {
        // The injector unwraps the underscores (_) from around the parameter names when matching
        $controller = _$controller_;
    }));

    describe('$scope.objectState', function () {
        it('is saying hello', function () {
            var $scope = {};
            var controller = $controller('yourController', { $scope: $scope });
            expect($scope.objectState).toEqual('hello');
        });
    });
});

언급URL : https://stackoverflow.com/questions/13013772/how-do-i-test-an-angularjs-service-with-jasmine

반응형