Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular tests: How to expect element events to be triggered?

I'm decorating forms like this:

angular.module('Validation').directive('form', function() {
  return {
    restrict: 'E',
    link: function(scope, element) {
      var inputs = element[0].querySelectorAll('[name]');

      element.on('submit', function() {
        for (var i = 0; i < inputs.length; i++) {
          angular.element(inputs[i]).triggerHandler('blur');
        }
      });
    }
  };
});

Now, I'm trying to test this directive:

describe('Directive: form', function() {
  beforeEach(module('Validation'));

  var $rootScope, $compile, scope, form, input, textarea;

  function compileElement(elementHtml) {
    scope = $rootScope.$new();
    form = angular.element(elementHtml);
    input = form.find('input');
    textarea = form.find('textarea');
    $compile(form)(scope);
    scope.$digest();
  }

  beforeEach(inject(function(_$rootScope_, _$compile_) {
    $rootScope = _$rootScope_;
    $compile = _$compile_;

    compileElement('<form><input type="text" name="email"><textarea name="message"></textarea></form>');
  }));

  it('should trigger "blur" on all inputs when submitted', function() {
    spyOn(input, 'trigger');
    form.triggerHandler('submit');
    expect(input.trigger).toHaveBeenCalled(); // Expected spy trigger to have been called.
  });
});

But, the test fails.

What's the right Angular way to test this directive?

like image 484
Misha Moroshko Avatar asked Dec 04 '25 01:12

Misha Moroshko


1 Answers

You have some problems:

1) input = form.find('input'); and angular.element(inputs[i]); are 2 different wrapper objects wrapping the same underlying DOM object.

2) You should create a spy on triggerHandler instead.

3) You're working directly with DOM which is difficult to unit-test.

An example of this is: angular.element(inputs[i]) is not injected so that we have difficulty faking it in our unit tests.

To ensure that point 1) returns the same object. We can fake the angular.element to return a pre-trained value which is the input = form.find('input');

//Jasmine 1.3: andCallFake
//Jasmine 2.0: and.callFake
angular.element = jasmine.createSpy("angular.element").and.callFake(function(){
         return input; //return the same object created by form.find('input');
});

Side note: As form is already an angularJs directive, to avoid conflicting with an already defined directive, you should create another directive and apply it to the form. Something like this:

<form mycustomdirective></form>

I'm not sure if this is necessary. Because we're spying on a global function (angular.element) which may be used in many places, we may need to save the previous function and restore it at the end of the test. Your complete source code looks like this:

it('should trigger "blur" on all inputs when submitted', function() {

    var angularElement = angular.element; //save previous function

    angular.element = jasmine.createSpy("angular.element").and.callFake(function(){
         return input;
    });

    spyOn(input, 'triggerHandler');
    form.triggerHandler('submit');

    angular.element = angularElement; //restore
    expect(input.triggerHandler).toHaveBeenCalled(); // Expected spy trigger to have been called.
  });

Running DEMO

like image 108
Khanh TO Avatar answered Dec 05 '25 21:12

Khanh TO