To test a custom validation angularjs directive

37,918

Solution 1

The other answer's tests should be written as:

describe('directives', function() {
  var $scope, form;
  beforeEach(module('exampleDirective'));
  beforeEach(inject(function($compile, $rootScope) {
    $scope = $rootScope;
    var element = angular.element(
      '<form name="form">' +
      '<input ng-model="model.somenum" name="somenum" integer />' +
      '</form>'
    );
    $scope.model = { somenum: null }
    $compile(element)($scope);
    form = $scope.form;
  }));

  describe('integer', function() {
    it('should pass with integer', function() {
      form.somenum.$setViewValue('3');
      $scope.$digest();
      expect($scope.model.somenum).toEqual('3');
      expect(form.somenum.$valid).toBe(true);
    });
    it('should not pass with string', function() {
      form.somenum.$setViewValue('a');
      $scope.$digest();
      expect($scope.model.somenum).toBeUndefined();
      expect(form.somenum.$valid).toBe(false);
    });
  });
});

Note that $scope.$digest() now is invoked after $setViewValue. This sets the form into “dirty” state, otherwise it would remain “pristine”, which probably is not what you want.

Solution 2

I figured it out by reading angular-app code https://github.com/angular-app/angular-app This video also helps too http://youtu.be/ZhfUv0spHCY?t=31m17s

Two mistakes that I made:

  • Do not bind directly to the scope when you are doing ng-model
  • Use form controller to directly manipulate what to pass for directives

Here is the updated version. The directive is the same, only the test that I changed.

describe('directives', function() {
  var $scope, form;
  beforeEach(module('exampleDirective'));
  beforeEach(inject(function($compile, $rootScope) {
    $scope = $rootScope;
    var element = angular.element(
      '<form name="form">' +
        '<input ng-model="model.somenum" name="somenum" integer />' +
      '</form>'
    );
    $scope.model = { somenum: null }
    $compile(element)($scope);
    $scope.$digest();
    form = $scope.form;
  }));

  describe('integer', function() {
    it('should pass with integer', function() {
      form.somenum.$setViewValue('3');
      expect($scope.model.somenum).toEqual('3');
      expect(form.somenum.$valid).toBe(true);
    });
    it('should not pass with string', function() {
      form.somenum.$setViewValue('a');
      expect($scope.model.somenum).toBeUndefined();
      expect(form.somenum.$valid).toBe(false);
    });
  });
});

Solution 3

I test my custom directives searching in the object "$error" the name of the custom validation. Example:

  'use strict';

describe('Directive: validadorCorreo', function () {

  // load the directive's module
  beforeEach(module('sistemaRegistroProCivilApp'));

  var inputCorreo, formulario, elementoFormulario, scope, $compile;

  beforeEach(inject(function ($rootScope, _$compile_) {
    scope = $rootScope.$new();
    $compile = _$compile_;

    elementoFormulario = angular.element('<form name="formulario">' + 
      '<input type="text" name="correo" data-ng-model="correo" required data-validador-correo/>' + 
      '</form');
    scope.correo = '';
    elementoFormulario = $compile(elementoFormulario)(scope);
    scope.$digest();
    inputCorreo = elementoFormulario.find('input');
    formulario = scope.formulario;
    console.log(formulario.correo.$error);
  }));

  it('Deberia Validar si un correo ingresado en el input es correcto e incorrecto', inject(function ($compile) {

    inputCorreo.val('[email protected]').triggerHandler('input');
    expect(formulario.correo.$error.email).toBe(true); //Here, the name of the custom validation appears in the $error object.
    console.log(formulario.correo.$error);

    inputCorreo.val('[email protected]').triggerHandler('input');
    expect(formulario.correo.$error.email).toBeUndefined();//Here, the name of the custom validation disappears in the $error object. Is Undefined
    console.log(formulario.correo.$error.email)
  }));
});

I Hope i can help you!

Share:
37,918
ghiden
Author by

ghiden

Updated on July 05, 2022

Comments

  • ghiden
    ghiden about 2 years

    This custom validation directive is an example presented at the official angular site. http://docs.angularjs.org/guide/forms It checks a text input is in number format or not.

    var INTEGER_REGEXP = /^\-?\d*$/;
    app.directive('integer', function() {
      return {
        require: 'ngModel',
        link: function(scope, elm, attrs, ctrl) {
          ctrl.$parsers.unshift(function(viewValue) {
            if (INTEGER_REGEXP.test(viewValue)) {
              // it is valid
              ctrl.$setValidity('integer', true);
              return viewValue;
            } else {
              // it is invalid, return undefined (no model update)
              ctrl.$setValidity('integer', false);
              return undefined;
            }
          });
        }
      };
    });
    

    To unit test this code, I wrote this:

    describe('directives', function() {
      beforeEach(module('exampleDirective'));
    
      describe('integer', function() {
        it('should validate an integer', function() {
          inject(function($compile, $rootScope) {
            var element = angular.element(
              '<form name="form">' +
                '<input ng-model="someNum" name="someNum" integer>' +
              '</form>'
              );
            $compile(element)($rootScope);
            $rootScope.$digest();
            element.find('input').val(5);
            expect($rootScope.someNum).toEqual(5);
          });
        });
      });
    });
    

    Then I get this error:

    Expected undefined to equal 5.
    Error: Expected undefined to equal 5.
    

    I put print statements everywhere to see what is going on, and it looks like the directive is never called. What is a proper way to test a simple directive like this?

  • thescientist
    thescientist almost 11 years
    @ghiden This really helped me, however, have you had any experiencing chaining multiple directives and testing that? Like if you made a directive for telephone or email? I get a bunch of error anytime I tried and add an additional directive. Thoughts?
  • ghiden
    ghiden almost 11 years
    Chaining multiple directives? I guess even if you apply multiple directives to one element, you still want to test them separately, correct? If each one works properly and priority is set correctly, I guess they should work. I sometimes do something like model validation first then proceed with visual effect directive with very low priority.
  • thescientist
    thescientist almost 11 years
    @ghiden I found my issue, I was testing a form validation directive, and using ng-model, and I believe my major was issue was also using name as the directive, as well as an attribute on the input element. I'm all good now though, and testing it! Thanks so much for your example though. Really helped a lot.
  • AndyPerlitch
    AndyPerlitch almost 10 years
    This wasn't working for me until I added $scope.$digest() after calling $setViewValue. Am I doing something weird or is this missing from you example? EDIT: sorry, did not see the answer below.
  • Mocksy
    Mocksy over 9 years
    i can never get the second test to pass (its always true) im using 1.3.12 did something change?
  • Florent Destremau
    Florent Destremau about 8 years
    Thank you so much, helped me out a lot today ! But I'm using directly the scope model rather than $setViewValue(), I don't know if I'm missing a lot of cases...?