12/08/2018, 13:23

AngularJS testing P2

Service là một trong những thành phần thường thấy nhất trong ứng dụng AngualarJS, Service cung cấp cách thức định nghĩa logic, có thể tái sử dụng mà không phải lặp đi lặp lại logic. Service có thể share dữ liệu thông qua nhiều controllers, directives và cả những services khác nữa Một ...

  • Service là một trong những thành phần thường thấy nhất trong ứng dụng AngualarJS, Service cung cấp cách thức định nghĩa logic, có thể tái sử dụng mà không phải lặp đi lặp lại logic.

  • Service có thể share dữ liệu thông qua nhiều controllers, directives và cả những services khác nữa

  • Một service có thể phụ thuộc vào nhiều services khác để cùng thực thì một tác vụ nào đó. Như vậy nhưng service liên quan ta có thể sử dụng mock trong quá trình viết test. Giả sử một service A phụ thuộc vào vào Service B, C nào đó. Vậy trong quá trình viết test có Service A ta có thể mock Service B và C.

  • Thông thường chúng ta thường mock hết các thành phần phụ thuộc, ngoại trừ một số services như $rootScope và $parse

Xét ví dụ:

angular.module('services', [])
  .service('sampleSvc', ['$window', 'modalSvc', function($window, modalSvc){
    this.showDialog = function(message, title){
      if(title){
        modalSvc.showModalDialog({
          title: title,
          message: message
        });
      } else {
        $window.alert(message);
      }
    };
  }]);
  • Service này có một phương thức showDialog. Nó sẽ gọi 1 hoặc 2 services $window hoặc modalSvc
  • Và bây giờ để test được service này ta cần phải mock 2 services phụ thuộc này load angular_module có chứa services và tất cả các objects liên quan.
var mockWindow, mockModalSvc, sampleSvcObj;
beforeEach(function(){
  module(function($provide){
    $provide.service('$window', function(){
      this.alert= jasmine.createSpy('alert');
    });
    $provide.service('modalSvc', function(){
      this.showModalDialog = jasmine.createSpy('showModalDialog');
    });
  });
  module('services');
});

beforeEach(inject(function($window, modalSvc, sampleSvc){
  mockWindow=$window;
  mockModalSvc=modalSvc;
  sampleSvcObj=sampleSvc;
}));

  • Ta có thể viết 2 test case Khi không có title và có title
it('should show alert when title is not passed into showDialog', function(){
  var message="Some message";
  sampleSvcObj.showDialog(message);

  expect(mockWindow.alert).toHaveBeenCalledWith(message);
  expect(mockModalSvc.showModalDialog).not.toHaveBeenCalled();
});

it('should show modal when title is passed into showDialog', function(){
  var message="Some message";
  var title="Some title";
  sampleSvcObj.showDialog(message, title);

  expect(mockModalSvc.showModalDialog).toHaveBeenCalledWith({
    message: message,
    title: title
  });
  expect(mockWindow.alert).not.toHaveBeenCalled();
});

  • Provider phải được load trước khi ứng dụng chạy, mỗi khi giai đoạn cấu hình AngularJS kết thúc, việc tương tác với provider sẽ không được phép, dẫn đến hệ quả Provider chỉ được truy cập bên trong config block hoặc là các provider khác.
  • Xét ví dụ dưới đây, provider phụ thuộc vào constant appConstants và một provider khác anotherProvider
angular.module('providers', [])
  .provider('sample', function(appConstants, anotherProvider){

    this.configureOptions = function(options){
      if(options.allow){
        anotherProvider.register(appConstants.ALLOW);
      } else {
        anotherProvider.register(appConstants.DENY);
      }
    };

    this.$get = function(){};
  });

  • Trước khi test cho provider, chúng ta cần đảm bảo chắc chắn rằng module đã được load. Trong test, việc loading module được gọi đến khi một inject block được thực thi hoặc test đầu tiên được thực thi. Để load module trước khi test ta có thể sự dụng một empty test hoặc một empty inject block
beforeEach(module("providers"));
beforeEach(function(){
  module(function(anotherProvider, appConstants, sampleProvider){
    anotherProviderObj=anotherProvider;
    appConstantsObj=appConstants;
    sampleProviderObj=sampleProvider;
  });
});
beforeEach(inject()); // dùng để load module trước khi test

Ta có thể viết test case như sau:

it('should call register with allow', function(){
  sampleProviderObj.configureOptions({allow:true});
  expect(anotherProviderObj.register).toHaveBeenCalled();
  expect(anotherProviderObj.register).toHaveBeenCalledWith(appConstantsObj.ALLOW);
});
  • Quá trình set-up cho việc test trên controller cũng khác với Service. Chúng không được khởi tạo tự động khi load route or ng-controller directive được được compiled. Và chúng ta cũng không load được view trong quá trình test vì thế nên cần phải khởi tạo controller một cách thủ công khi test.
  • Thông thường thì controller phụ thuộc vào view, các methods trong controller cũng phụ thuộc vào view. Một vài objects có thể được thêm vào scope sau khi view được complied. Và để test được thì những objects này phải được tạo vào thêm vào controller một cách thủ công

ví dụ

angular.module('controllers',[])
  .controller('FirstController', ['$scope','dataSvc', function($scope, dataSvc) {
    $scope.saveData = function () {
      dataSvc.save($scope.bookDetails).then(function (result) {
        $scope.bookDetails = {};
        $scope.bookForm.$setPristine();
      });
    };

    $scope.numberPattern = /^d*$/;
  }]);
  • Để test được controller ta cần tạo ra một instance của controller một params $$cope và mock service dataSvc

  • Mock cho dataSvc service

module(function($provide){
  $provide.factory('dataSvc', ['$q', function($q)
    function save(data){
      if(passPromise){
        return $q.when();
      } else {
        return $q.reject();
      }
    }
    return{
      save: save
    };
  }]);
});
  • Sau đó ta có thể tạo một new scope mới cho controller bằng cách sử dụng rootScope.rootScope.rootScope.new
beforeEach(inject(function($rootScope, $controller, dataSvc){
  scope=$rootScope.$new();
  mockDataSvc=dataSvc;
  spyOn(mockDataSvc,'save').andCallThrough();
  firstController = $controller('FirstController', {
    $scope: scope,
    dataSvc: mockDataSvc
  });
}));

Spec cho test regex

it('should have assigned right pattern to numberPattern', function(){
    expect(scope.numberPattern).toBeDefined();
    expect(scope.numberPattern.test("10")).toBe(true);
    expect(scope.numberPattern.test("abc")).toBe(false);
});
  • Để test cho phương thức saveData, ta cần phải gán giá trị cho bookDetails và bookForm. Những object này được được bao bọc bởi các thành phần UI, vi thế nên cần phải được tạo tại thời điểm runtime và khi view được compiled.
it('should call save method on dataSvc on calling saveData', function(){
    scope.bookDetails = {
      bookId: 1,
      name: "AngularJS"
    };
    scope.bookForm = {
      $setPristine: jasmine.createSpy('$setPristine')
    };
    passPromise = true;
    scope.saveData();
    scope.$digest();
    expect(mockDataSvc.save).toHaveBeenCalled();
    expect(scope.bookDetails).toEqual({});
    expect(scope.bookForm.$setPristine).toHaveBeenCalled();
});

Kết

Nguồn tham khảo http://http            </div>
            
            <div class=

0