单元测试Angular指令访问外部元素

时间:2015-12-09 23:03:40

标签: angularjs unit-testing angularjs-directive

我有一个自定义指令,它使用一个属性来指定它修改的另一个控件。

指令定义对象:

{
    restrict: 'E',
    templateUrl: 'myTemplate.html',
    scope: {
        targetId: '@'
    },
    controller: MyController,
    controllerAs: 'vm',
    bindToController: true
}

指令控制器上的函数修改目标元素的内容(输入字段):

function onSelection (value) {
    var $element = $('#' + vm.targetId);

    $element.val('calculated stuff');
    $element.trigger('input');
}

单元测试(Jasmine / Karma / PhantomJS)当前将元素附加到页面。这有效,但它看起来像是一种代码味道。

beforeEach(inject(function($rootScope, $compile) {
    var elementHtml = '<my-directive target-id="bar"></my-directive>' +
        '<input type="text" id="bar">';

    scope = $rootScope.$new();    
    angularElement = angular.element(elementHtml);
    angularElement.appendTo(document.body);  // HELP ME KILL THIS!
    element = $compile(angularElement)(scope);
    scope.$digest();
}));

afterEach(function () {
    angularElement.remove();  // HELP ME KILL THIS!
});

我尝试重写控制器函数以避免jQuery;这没有帮助。

如何修改指令或测试以消除appendTo / remove?

2 个答案:

答案 0 :(得分:2)

最好的办法是将指令迁移到属性而不是元素。这消除了对target-id属性的需要,您不需要寻找目标元素。

请参阅http://jsfiddle.net/morloch/621rp33L/

<强>指令

angular.module('testApp', [])
  .directive('myDirective', function() {
    var targetElement;
    function MyController() {
      var vm = this;
      vm.onSelection = function() {
        targetElement.val('calculated stuff');
        targetElement.trigger('input');
      }
    }
    return {
      template: '<div></div>',
      restrict: 'A',
      scope: {
        targetId: '@'
      },
      link: function postLink(scope, element, attrs) {
        targetElement = element;
      },
      controller: MyController,
      controllerAs: 'vm',
      bindToController: true
    };
  });

<强>测试

describe('Directive: myDirective', function() {
  // load the directive's module
  beforeEach(module('testApp'));

  var element, controller, scope;

  beforeEach(inject(function($rootScope, $compile) {
    scope = $rootScope.$new();
    element = angular.element('<input my-directive type="text" id="bar">');
    $compile(element)(scope);
    scope.$digest();
    controller = element.controller('myDirective');
  }));

  it('should have an empty val', inject(function() {
    expect(element.val()).toBe('');
  }));

  it('should have a calculated val after select', inject(function() {
    controller.onSelection();
    expect(element.val()).toBe('calculated stuff');
  }));
});

答案 1 :(得分:1)

这是另一个保持逻辑几乎完全相同的建议:使用第二个指令使控制器上的目标element可用,然后您可以将其传递给主指令进行处理:http://jsfiddle.net/morloch/p8r2Lz1L/

<强> getElement

  .directive('getElement', function() {
    return {
      restrict: 'A',
      scope: {
        getElement: '='
      },
      link: function postLink(scope, element, attrs) {
        scope.getElement = element;
      }
    };
  })

<强> myDirective

  .directive('myDirective', function() {
    function MyController() {
      var vm = this;
      vm.onSelection = function() {
        vm.targetElement.val('calculated stuff');
        vm.targetElement.trigger('input');
      }
    }
    return {
      template: '<div></div>',
      restrict: 'E',
      scope: {
        targetElement: '='
      },
      controller: MyController,
      controllerAs: 'vm',
      bindToController: true
    };
  })

<强>测试

describe('Directive: myDirective', function() {
  // load the directive's module
  beforeEach(module('testApp'));

  var element, controller, scope;

  beforeEach(inject(function($rootScope, $compile) {
    scope = $rootScope.$new();
    element = angular.element('<input get-element="elementBar" type="text" id="bar"><my-directive target-element="elementBar"></my-directive>');
    $compile(element)(scope);
    scope.$digest();
    controller = $(element[1]).controller('myDirective');
  }));

  it('should have an empty val', inject(function() {
    expect($(element[0]).val()).toBe('');
  }));

  it('should have a calculated val after select', inject(function() {
    controller.onSelection();
    expect($(element[0]).val()).toBe('calculated stuff');
  }));
});