잊지 않겠습니다.

directive는 test하기에 가장 어려운 객체입니다. VIEW의 html element이 특정 HTML로 변경되는 process를 통과해야지 되며, 이 HTML에 event가 발생했을 때의 동작을 모두 테스트해야지 되기 때문입니다. 다음과 같은 directive code를 sample로 한번 알아보도록 하겠습니다.
angular.module('fmsmobilewebApp').directive('codeDropdown', function (ClassificationService, $timeout) {
  return {
    template: '<select class="ds-select" ng-model=ngModel ng-options="o.id as o.name for o in optionData" ></select> <button ng-click="load()">로드</button>',
    restrict: 'E',
    scope: {
      ngModel: '=',
      codeType: '@',
      parentCodeId: '@',
      styleValue: '@',
      emptyData:'@'
    },
    link: function postLink(scope, element, attrs) {
      scope.optionData = [];

      var init = function() {
        ClassificationService.getList(scope.codeType, scope.parentCodeId, listup);
      };

      scope.load = function() {
        init();
      };

      var listup = function(codes) {
        var items = [];
        if(scope.emptyData) {
          items.push({ text: scope.emptyData, value: ''});
        }
        for(var i = 0 ; i < codes.length ; i++) {
          items.push(codes[i]);
        }
        if(scope.ngModel == null || scope.ngModel == '') {
          scope.ngModel = items[0].value;
        }
        if(scope.styleValue != null && scope.styleValue != '') {
          $(element).find('select').attr('style', scope.styleValue);
        }
        scope.optionData = items;
        $timeout(function() {
          scope.$apply();
        });
      };
    }
  };
});

위 directive는 comboBox과 button을 표시하는 directive입니다. button을 click하면 load method가 호출되는 구조로 되어 있습니다.
scope의 형태를 한번 봐주시길 바랍니다.

이 directive는 다음과 같은 특징을 가지고 있습니다.

* Service에 대한 dependency를 갖는다.
* VIEW(html) 요소를 directive의 template으로 치환한다.
* controller의 scope의 value에 대한 bi-direction을 갖는 scope variable이 존재한다. (ng-model)
* button에 대한 외부 event가 존재한다.
  <button ng-click="load()">로드</button>

이와 같은 directive를 테스트 하기 위한 방법에 대해서 알아보도록 하겠습니다.

Service에 대한 dependency

directive는 생성되는 시점이 controller나 service와는 조금 다릅니다. controller는 URL의 호출시에 controller를 생성시키지만, directive의 경우에는 page의 loading이 다 마쳐진 이후에 $compile 과정을 통해서 directive가 생성되게 됩니다. 따라서, directive를 사용하기 위해서 필요한 html element, css의 선언이전에 dependency를 inject를 해야지 됩니다.

따라서, beforeEach에서 provider를 얻어내, service를 재정의해서 inject할 필요가 있습니다. 다음은 위 directive의 ClassificationService를 재정의하는 code입니다.

  beforeEach(module('fmsmobilewebApp', function($provide) {
    var classificationService = {
        getList: function(codeType, parentCodeId, func) {
          var codes = [];
          for(var i = 0 ; i < 20 ; i++) {
            codes.push({ id: 'id' + i.toString(), name: 'name-' + i.toString() });
          }
          if(func) {
            func(codes);
          }
          return true;
        }
      };
    $provide.value('ClassificationService', classificationService);
  }));

provider에 사용되는 ClassificationService를 setting해주는 형태입니다. 기본적으로 unit test는 whitebox test기법이기 때문에, 우리가 사용하는 service와 그 method에 대한 정보는 아는 것으로 가정하기 때문에 최소한으로 필요한 service code만을 재정의해서 만들어주는 것이 좋습니다.

VIEW(html)의 치환

지금까지 모든 angularJS의 unit test는 test 대상이 되는 객체를 얻어와서 test를 진행했습니다. 그렇지만 directive의 경우에는 좀 다릅니다. directive의 생성은 html이 있어야지 됩니다. html을 치환하는 과정을 compile이라고 합니다. 다음은 compile을 진행하는 과정을 test code에서 나타냅니다.

  it('code dropdown test code', inject(function ($compile) {
    element = angular.element('<code-dropdown ng-model="ngModel" code-type="system" parent-code-id="0801"></code-dropdown>');
    element = $compile(element)(scope);
    // display directive's compiled html
    console.log(element.html());
  });

눈으로 html을 보게 되면, 정상적인 test code라고 할 수 없습니다. 다음과 같이 만들어져야지 되는 html element의 숫자를 확인하는 것이 좋습니다.

    element = angular.element('<code-dropdown ng-model="ngModel" code-type="system" parent-code-id="0801"></code-dropdown>');
    element = $compile(element)(scope);
    //sync parent scope and directive scope
    scope.$digest();
    expect(directiveScope.optionData.length).not.toBe(0);
    expect(element.find('option').length).toBe(20);

scope의 binding

directive의 scope의 value는 크게 두가지 type이 있습니다. bi-direction으로 동작하는 scope의 property와 내부에서만 사용되는 isolateScope가 바로 그것입니다. 기본적으로 directive는 scope의 type이 =으로 지정된 경우를 제외하고 isolateScope를 생성하게 됩니다. directive내부의 scope를 접근하기 위해서는 기존 test code에서 사용한 scope로는 접근이 불가능합니다. 이 부분이 기존의 controller code와의 가장 큰 차이가 됩니다.

directive의 scope를 얻어내는 test code는 다음과 같습니다.

    element = angular.element('<code-dropdown ng-model="ngModel" code-type="system" parent-code-id="0801"></code-dropdown>');
    element = $compile(element)(scope);
    //sync parent scope and directive scope
    scope.$digest();

    //access directive scope
    expect(element.isolateScope).toBeDefined();
    expect(element.isolateScope()).not.toBeNull();

    var directiveScope = element.isolateScope();
    expect(directiveScope.ngModel).toBe('id0');

    //change parent scope value
    scope.ngModel = 'id2';
    scope.$digest();
    expect(directiveScope.ngModel).toBe('id2');

    directiveScope.ngModel = 'id1';
    directiveScope.$digest();
    expect(scope.ngModel).toBe('id1');

마지막 6 line을 주의깊게 볼 필요가 있습니다. parent scope value와 bi-direction 되어 있기 때문에 parent scope의 값을 변경하거나, directive의 scope 값을 변경하는 것 모두 양쪽에 영향을 주고 있는 것을 볼 수 있습니다.

button과 같은 event가 binding 되어 있는 경우

button과 같은 사용자의 event가 binding이 되어 있는 경우에는 event에 대한 처리를 code로 구성할 수 있습니다.
다음 test code를 보는 것이 설명이 더 빠릅니다.

    //Click Event
    element.find('button')[0].click();
    expect(directiveScope.optionData.length).not.toBe(0);
    expect(element.find('option').length).toBe(20);

element에서 event를 handling시킬 button을 찾아낸 후, click event를 전달할 수 있습니다.

추가: 외부 html을 template으로 사용하는 경우

directive를 작성할 때, template을 html 파일로 따로 관리하는 경우가 많습니다. directive의 크기가 너무 크거나, html로 관리하는 것이 관리 focus상 유리할 때 종종 이런 방법을 택하지요. 외부 html을 사용하는 경우, directive code는 다음과 같이 구성됩니다.

angular.module('fmsmobilewebApp').directive('loading', function () {
  return {
    templateUrl:'templates/loading.html',
    restrict: 'E',
    require: '^ngModel',
    transclude: true,
    scope: {
      ngModel: '='
    }

  };
});

여기서 문제가 되는 것은 templateUrl의 위치는 yo기준으로 app folder위치의 상대값입니다. 그렇지만 우리가 grunt test를 통해서 테스트를 실행하는 path는 ../app 이 됩니다. 따라서, 저 url은 절대로 로드할 수 없는 위치가 됩니다. 이 경우, 다음과 같은 karma.conf.js 수정이 필요합니다.

먼저, loading 해야지 되는 html을 files에 추가합니다.

    files: [
      // bower components
      'app/bower_components/jquery/dist/jquery.js',
      'app/bower_components/angular/angular.js',
      'app/bower_components/angular-mocks/angular-mocks.js',
      'app/bower_components/angular-resource/angular-resource.js',
      'app/bower_components/angular-cookies/angular-cookies.js',
      'app/bower_components/angular-sanitize/angular-sanitize.js',
      'app/bower_components/angular-route/angular-route.js',
      'app/bower_components/angular-touch/angular-touch.min.js',
      'app/bower_components/lodash/dist/lodash.min.js',
      'app/bower_components/mobile-angular-ui/dist/js/mobile-angular-ui.min.js',
      'app/bower_components/restangular/dist/restangular.min.js',
      'app/bower_components/angular-local-storage/angular-local-storage.min.js',
      // 3rd party library
      'app/library/kendo/js/kendo.all.min.js',
      // Test target
      'app/scripts/*.js',
      'app/scripts/**/*.js',
      // Test code
      'test/mock/**/*.js',
      'test/spec/**/*.js',
      // Template Files
      'app/templates/*.html'
    ],

다음에, preprocessor 단계에서 html로 로딩된 항목을 javascript로 변경시켜, cache에 저장합니다.

    preprocessors: {
      'app/templates/*.html': 'html2js'
    },

이제 저장된 html은 $templateCache를 통해서 얻어올 수 있게 됩니다. test code의 beforeEach에서 다음과 같이 처리해줍니다.

  beforeEach(inject(function($templateCache) {
    template = $templateCache.get('app/templates/loading.html');
    expect(template).not.toBeNull();
    $templateCache.put('templates/loading.html', template);
  }));

$templateCache에서 먼저 로딩된 template을 읽어주고, directive에서 사용하는 templateUrl 값에 그 값을 넣어주면, directive가 $compile될 때 templateCache값을 참고해서 처리하게 됩니다.

Summary

지금까지 angularJS를 이용한 web application의 unit test 방법에 대해서 알아봤습니다. 약간의 정리를 하자면, 다음 원칙을 지켜가면서 test code를 작성하면 좋습니다. 이는 개발 code 작성도 비슷한 느낌이 될 수 있을것입니다.

  1. filter, non HTTP service 부터 test 및 code를 작성합니다.
  2. HTTP service의 경우, 호출되는 URL과 parameter에 대한 test를 작성합니다.
  3. HTTP service의 경우, input이 json type인 경우에는 input data에 대한 validator를 따로 작성해서 test하는 것이 좋습니다.
  4. Controller는 사용자 scenario를 정해서 TDD(test driven development)로 작성하는 것이 좋습니다.

제일 중요한 것은 unit test는 white box test입니다. 개발자가 내용을 다 안다는 것을 가정하고 있습니다. 최종적인 UI에 대한 test는 반드시 하는 것이 좋습니다. unit test는 UI에 대한 통합 테스트를 진행하기 쉽게 좀더 신경쓸 내용들을 줄이는 용도로 사용하는 것이지, UI에 대한 통합 테스트를 대치할 수는 없습니다.

모두들 Happy coding 하세요.


Posted by Y2K
,
이번에는 Controller입니다. Controller는 view에서의 사용자의 action에 따른 scenario가 정해지게 됩니다. scenario에 따른 test code를 작성해주는 것이 가장 좋은 test code가 됩니다.
* page가 처음 load되면 list가 호출되어 기본 Data가 Loading된다. (AsService.list()가 호출)
* search button을 click시, list() method가 호출된다. (AsService.list()가 호출)
* startDate/endDate 값이 변경되면 list method가 호출된다.
* save button을 click시, confirm box가 표시되며, input값이 모두 정상적으로 입력되어 있지 않다면 error가 표시된다.

첫 case부터 시작해보도록 하겠습니다.

page가 처음 load되면 list() method가 호출된다.

page가 처음 load되는 것은 controller가 처음 생성되는 것을 의미합니다. 이는 beforeEach()에 대한 test code가 필요한 것을 의미합니다. list가 호출이 될 때, REST API를 호출하는 것을 가정하고 있기 때문에 REST API의 응답에 대한 mock response를 넣어주는 것이 필요합니다.

controller를 생성하기 위한 beforeEach와 createController 의 코드는 다음과 같이 구성될 수 있습니다.

  beforeEach(inject(function ($controller, $rootScope, _DateService_, $httpBackend) {
    httpBackend = $httpBackend;
    scope = $rootScope.$new();
    DateService = _DateService_;
    controller = $controller;
    OmAsListCtrl = createController();
  }));

  var createController = function() {
    var response = {"ok":true,"message":"api call completed","date":1413173458673,"data":[{"request":"김요청자","operators":"","requestPhoneNumber":"0212342341","receiptDate":"2014-08-27","name":"오피스를 더 좋게 해주세요.","receiptionPhoneNumber":"03121342134","location":"없음","id":"30221","receiption":"김접수자","departments":"관리팀,건축팀,관제팀,기계팀,소방팀","department":"-","status":"접수"},{"request":"박네임","requestPhoneNumber":"01015263748","departmentId":"0401","receiptDate":"2014-05-05","name":"13층 환기 안됨","receiptionPhoneNumber":"01056781234","location":"13층","id":"6","receiption":"이성명","departments":"관리팀,기계팀","department":"관리팀","status":"민원완료"}]};
    httpBackend.when('GET', /\/fms-api\/om\/as\/list?\W*/).respond(response);
    var ctrl = controller('OmAsListCtrl', {
      $scope: scope
    });
    scope.$digest();
    httpBackend.flush();
    expect(scope.items.length).not.toBe(0);
    return ctrl;
  };

scope.items의 갯수를 확인하고, 정확히 데이터가 matching되고 있는 것을 확인하면 load의 test code는 완료됩니다.

search button을 click시, list() method가 호출된다.

list button을 click하는 것은 $scope.list()가 호출되는 것을 의미합니다. 호출후에 items의 갯수를 확인해보는 것이 가장 쉬운 확인 방법입니다. test를 보다 쉽게 작성하기 위해서 list를 호출하기 전에 items를 초기화 시켜버린 후에 list를 호출한 후의 값을 비교해보면 확실히 알 수 있을것입니다.

  it('search button을 click시, list() method가 호출된다.', function() {
    scope.items = [];
    var response = {"ok":true,"message":"api call completed","date":1413173458673,"data":[{"request":"김요청자","operators":"","requestPhoneNumber":"0212342341","receiptDate":"2014-08-27","name":"오피스를 더 좋게 해주세요.","receiptionPhoneNumber":"03121342134","location":"없음","id":"30221","receiption":"김접수자","departments":"관리팀,건축팀,관제팀,기계팀,소방팀","department":"-","status":"접수"},{"request":"박네임","requestPhoneNumber":"01015263748","departmentId":"0401","receiptDate":"2014-05-05","name":"13층 환기 안됨","receiptionPhoneNumber":"01056781234","location":"13층","id":"6","receiption":"이성명","departments":"관리팀,기계팀","department":"관리팀","status":"민원완료"}]};
    httpBackend.when('GET', /\/fms-api\/om\/as\/list?\W*/).respond(response);
    scope.list();
    httpBackend.flush();
    expect(scope.items.length).not.toBe(0);
  });

startDate/endDate값이 변경되면 list method가 호출된다.

이는 spyOn을 이용하면 쉽게 처리가 가능합니다. 다음과 같은 test code를 작성할 수 있습니다.

  it('startDate가 변경되면, list가 호출되어야지 된다.', function() {
    spyOn(scope, 'list');
    scope.searchItem.startDate = '2015-05-01';
    scope.$digest();
    expect(scope.list).toHaveBeenCalled();
  });

  it('endDate가 변경되면, list가 호출되어야지 된다.', function() {
    spyOn(scope, 'list');
    scope.searchItem.endDate = '2015-05-01';
    scope.$digest();
    expect(scope.list).toHaveBeenCalled();
  });

save button을 click시, confirm이 표시되고, confirm true인 경우에 AsService.regist method가 호출된다.

confirm, alert은 browser가 제공하는 base dialog method입니다. 만약에 우리가 test code를 실행할때마다 dialog가 나오고, 그걸 수동으로 눌러줘야지 된다면 이는 매우 걸리적 거리는 일이 됩니다. 따라서, 이 method들을 override 시킬 필요가 있습니다. angularJS는 이를 위해서 $window를 제공하고 있습니다. 우리가 $window.confirm, $window.alert method를 override 시켜서 사용하면 confirm에서 항상 true만 선택하게 할 수 있습니다.

  beforeEach(inject(function ($controller, $rootScope, _$routeParams_, _$location_, _$window_) {
    routeParams = _$routeParams_;
    routeParams.id = '10';
    scope = $rootScope.$new();
    controller = $controller;
    window = _$window_;
    window.confirm = function(message) {
      console.log(message);
      return true;
    };
  }));

routeParam의 값에 따라 다른 동작을 하는 controller의 테스트

위 scenario에 없는 parameter값에 따라 다른 동작을 하는 controller를 test하는 방법입니다. 이는 기본적으로 $routeParam에 값을 설정시키고 나서 controller를 생성시켜주면 됩니다. 다음과 같은 test code pattern을 적용하면 됩니다.

  function createCtrl(ymd, type) {
    if(ymd) {
      routeParams.ymd = ymd;
    }
    if(type) {
      routeParams.type = type;
    }

    ctrl = controller('EmDailymeterListCtrl', {
      $scope: scope,
      $routeParams: routeParams,
      Dailymeterservice: service
    });
    if(ymd == null || type == 'list') {
      httpBackend.expectGET(/\/fms-api\/em\/dailymeter\/list?\W*/).respond(200, {});
    } else {
      httpBackend.expectGET(/\/fms-api\/em\/dailymeter\/new?\W*/).respond(200, {});
    }
    scope.$digest();
    httpBackend.flush();
    return ctrl;
  }


Posted by Y2K
,

service (non-HTTP)

angularJS의 service code는 일반적으로 REST API에 대한 호출을 하고, 그 결과를 받는 것을 담당하지만, 값의 변경이나 특정값들의 display 형식으로의 변경등을 service로 만들어서, 코드의 응집력을 높일 수 있습니다. 섭씨온도를 화씨온도로 바꾸는 등의 계산 로직도 역시 이 영역에 들어가게 됩니다.

이러한 service code의 테스트는 다음 항목을 확인하면 됩니다.

input 값에 대하여 output 값이 예상대로 나왔는지. 
> toBe(value) 구문을 이용합니다.

기본적으로 matcher를 사용하는 것이 point입니다. 예상한 값을 기준으로 원하는 output이 정상적으로 나오는지를 확인하는 것이 좋습니다.

service (with HTTP)

REST API를 사용하는 service code의 테스트 방법에 대해서 알아보도록 하겠습니다. 일단 REST API를 호출한다면 외부의 HTTP resource를 이용하게 됩니다. 외부의 자원을 이용한다는 것은 그 외부의 자원에 대한 통제권은 우리가 개발한 application에는 없다는 말과 동일합니다. 따라서, 우리가 REST API를 이용하는 service에서 확인할 내용은 다음 두가지입니다.

* 원하는 URL로 정상적으로 호출하고 있는지?
* 전달하는 parameter가 API 호출 규약에 맞는지?

여기서 두가지의 역활이 나뉘게 됩니다. 먼저 원하는 URL로 호출하는 것은 당연히 service가 하는 일입니다. 그런데, 전달하는 parameter가 호출규약에 맞는지는 어떻게 확인하는 것이 좋을까요. 우리가 Spring을 이용한 개발을 진행하였을때를 생각해보면 쉽게 결론이 나옵니다. Validator객체를 만들어서 사용하는 것입니다.

따라서, REST API를 호출하는 service의 코드는 두개의 객체로 나뉘어지면 테스트 및 개발이 원활하게 됩니다. 이와 같은 형태는 마치 Spring MVC에서의 Controller 객체와 Validator 객체를 따로 개발하는 것과 동일한 패턴을 가지고 오게 됩니다. 객체지향이라는 것이 궁극적으로는 서로간에 할 일을 명확히 구분지어주고 그 분류대로 일을 진행하는 것이니까요.

먼저, 간단한 validator code는 다음과 같습니다.

'use strict';

/**
 * @ngdoc service
 * @name fmsmobilewebApp.ResultRegisterValidatorService
 * @description
 * # ResultRegisterValidatorService
 * Service in the fmsmobilewebApp.
 */
angular.module('fmsmobilewebApp').service('ResultRegisterValidatorService', function ResultRegisterValidatorService() {
  var self = this;

  self.validateStatus = function(data) {
    return angular.isDefined(data.status);
  };

  self.validateDepartment = function(data) {
    return angular.isDefined(data.departmentId);
  };

  self.validateOpResult = function(data) {
    var checked = angular.isDefined(data.opResult) &&
                  angular.isDefined(data.opResult.content) &&
                  angular.isDefined(data.opResult.startDate) &&
                  angular.isDefined(data.opResult.endDate);
    return checked;
  };
});

그리고 이에 대한 테스트 코드는 다음과 같습니다.

'use strict';

describe('Service: ResultRegisterValidatorService', function () {

  // load the service's module
  beforeEach(module('fmsmobilewebApp'));

  // instantiate service
  var service;
  beforeEach(inject(function (_ResultRegisterValidatorService_) {
    service = _ResultRegisterValidatorService_;
  }));

  it('validate for opResult', function() {
    var data = {};
    expect(service.validateOpResult(data)).toBe(false);
    data.opResult = {};
    expect(service.validateOpResult(data)).toBe(false);
    data.opResult.content = 'Content';
    expect(service.validateOpResult(data)).toBe(false);
    data.opResult.startDate = '2014-04-05 12:00';
    expect(service.validateOpResult(data)).toBe(false);
    data.opResult.endDate = '';
    expect(service.validateOpResult(data)).toBe(true);
  });

  it('validate for status', function() {
    var data = {};
    expect(service.validateStatus(data)).toBe(false);
    data.status = '0701';
    expect(service.validateStatus(data)).toBe(true);
  });

  it('validate for department', function() {
    var data = {};
    expect(service.validateDepartment(data)).toBe(false);
    data.departmentId = '0401';
    expect(service.validateDepartment(data)).toBe(true);
  });

});

Validator의 test code의 경우에는 기본적인 service code와 동일합니다. input에 따른 결과가 어떻게 나오는지만 확인하면 됩니다.

이제 가장 중요한 http에 대한 서비스 코드입니다. 우리는 test code를 작성하기 전에 REST API를 호출하는 행위에 대한 정의가 우선 필요합니다.
REST API를 호출하는 것은 다음과 같습니다.

  1. URL에 대한 HTTP request
  2. method에 따른 HTTP request
  3. HTTP response에 대한 결과값에 대한 parsing

결국은 우리가 확인할 것은 위 3가지입니다. 그리고 중점을 줘야지 되는 것을 따지자면 1, 2에 대한 확인입니다. 3에 대한 내용은 REST API의 호출 특성상, API 서버측에서 보내는 결과이기 때문에 그 결과값이 json format인 경우, 우리가 테스트를 해서 확인할 내용이 아닙니다. 우리는 그 결과값을 온전히 받아서 사용하는 client측에서만 생각하면 되기 때문에 1, 2번에 대한 내용만을 테스트한다면 우리가 원하는 test code를 작성할 수 있습니다.

angularJS는 angular-mock을 통해 $httpBackend 객체에 우리가 호출되는 URL, method를 확인할 수 있는 방법을 제공합니다. 일단 다음 상황을 가정해보도록 합니다.

GET /api/as/list

그리고, 이러한 url을 호출하는 API Service를 작성합니다.

function AsService(Restangular, AsServiceValidator) {
  var self = this;
  self.getList = function(status, department, func) {
    if(AsServiceValidator.checkStatus(status) && AsService.checkDepartment(department)) {
      Restangular.all('/api/as/list').get().then(function(jsonResult) {
        if(jsonResult.ok && func) {
          func(jsonResult);
        }
      });
      return true;
    } 
    return false;
  };
}

위 코드는 그 전에 이야기한 validator에 대한 체크 후, Restangular를 이용한 http call을 하고 있습니다. service에 대한 test code를 작성할 때, 반드시 $httpBackend에 대한 expect를 설정해야지 됩니다. 다음은 test code의 일부분입니다.

var httpBackend, service
beforeEach(inject(function($httpBackend, AsService) {
  httpBackend = $httpBackend;
  service = AsService;
}));

위 코드패턴을 이용해서 우리는 테스트의 대상이 되는 service와 $httpBackend를 얻어올 수 있습니다. 이제 우리가 원하는 API를 정상적으로 호출하고 있는지를 확인해보도록 하겠습니다.

it('as list REST API 호출', function() {
  httpBackend.expectGET(/\/api\/as\/list/\W*).respond('{ ok: true, data: null, message: data }');
  service.getList(status, department, function(jsonResult) {
    console.log(jsonResult);
  });
  httpBackend.flush();
});

REST API를 호출하는 service의 test code pattern은 다음과 같습니다.

  1. expectGET/expectPOST/expectDELETE/expectPUT method를 이용해서 request가 전달될 URL을 예상합니다.
  2. service를 호출합니다.
  3. httpBackend.flush() method를 호출합니다.

여기서 주의할점은 httpBackend.flush() 입니다. flush가 호출되게 되면, httpBackend는 httpRequest에 대한 response를 발생시킵니다. 만약에 expect에 정의되지 않은 http request가 발생되게 되면 exception을 발생하게 됩니다. REST API의 response를 명확히 알고 있다면, 그 결과값에 대한 처리 로직도 테스트가 가능합니다.

REST API service에 대한 test code작성방법에 대해서 다시 한번 정의해보도록 하겠습니다.

  1. input에 대한 validator의 분리가 필요 > validator 테스트 코드
  2. $httpBackend.expectXXX method를 이용한 REST API 호출 확인

이 두가지에 중점을 주면 이제 service code 역시 테스트가 가능하게 됩니다.


Posted by Y2K
,