Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit test Angular directive that accesses external element

I have a custom directive that uses an attribute to specify another control that it modifies.

Directive definition object:

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

A function on the directive's controller modifies the contents of the target element (an input field):

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

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

The unit tests (Jasmine/Karma/PhantomJS) currently append the element to the page. This works, but it seems like a code smell.

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!
});

I've tried rewriting the controller function to avoid jQuery; this did not help.

How can I revise the directive or the tests to eliminate the appendTo/remove?

like image 878
TrueWill Avatar asked Dec 09 '15 23:12

TrueWill


People also ask

What is Componentfixture in Angular testing?

You can create a component fixture with TestBed. createComponent . Fixtures have access to a debugElement , which will give you access to the internals of the component fixture. Change detection isn't done automatically, so you'll call detectChanges on a fixture to tell Angular to run change detection.

How do you test a jasmine directive?

We should Unit Test directives by mocking all dependencies with jasmine mocks and spies. We should also Shallow / Deep Test directives using concrete Components (Compiled DOM). A reasonable approach is to create TestComponent or pick up any component which uses the directive we want to test.

What is TestBed in Angular?

TestBed is the primary api for writing unit tests for Angular applications and libraries.

What is fixture detectChanges?

fixture is a wrapper for our component's environment so we can control things like change detection. To trigger change detection we call the function fixture.detectChanges() , now we can update our test spec to: Copy it('login button hidden when the user is authenticated', () => { expect(el. nativeElement. textContent.


1 Answers

Your best bet is to migrate the directive to an attribute instead of an element. This removes the need for the target-id attribute and you don't need to hunt for the target element.

See http://jsfiddle.net/morloch/621rp33L/

Directive

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
    };
  });

Test

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');
  }));
});
like image 197
morloch Avatar answered Nov 15 '22 06:11

morloch