测试自定义验证的AngularJS指令

76
这个自定义验证指令是官方angular网站上的一个示例。 http://docs.angularjs.org/guide/forms 它检查文本输入是否为数字格式。
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;
        }
      });
    }
  };
});

为了对这段代码进行单元测试,我写了这个:
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);
      });
    });
  });
});

然后我收到了这个错误:

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

我在各处添加打印语句以查看情况,看起来指令从未被调用。测试这样一个简单指令的正确方法是什么?

感谢您抽出时间回答问题!只是提醒一下,您可以提取您的答案并将其标记为被接受的答案,以供后来的搜索者查找 - 这在这里是可以接受的;-) - Sean Vieira
谢谢您的建议。我已经移动了我的回答。 - ghiden
3个回答

88

其他答案的测试应该写成:

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

请注意,现在$setViewValue之后会调用$scope.$digest()。这将使表单进入“脏”状态,否则它将保持“原始”,这可能不是您想要的。


非常感谢,今天帮了我很多的忙!但是我正在使用直接作用域模型而不是$setViewValue(),我不知道是否会错过很多情况...? - Florent Destremau

67
我通过阅读angular-app代码 https://github.com/angular-app/angular-app 才明白了这个问题。这个视频也很有帮助 http://youtu.be/ZhfUv0spHCY?t=31m17s
我犯了两个错误:
  • 当你使用ng-model时,不要直接绑定到作用域上。
  • 使用表单控制器直接操作指令所需传递的内容。
这是更新后的版本。指令本身没有变化,只是我修改了测试。
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);
    });
  });
});

@ghiden 这确实帮了我很多,但是你有没有尝试过链接多个指令并进行测试?比如为电话或电子邮件创建指令?每当我尝试添加其他指令时都会出现一堆错误。你有什么想法吗? - thescientist
链接多个指令?我猜即使您将多个指令应用于一个元素,您仍然希望单独测试它们,对吗?如果每个指令都正常工作并且优先级设置正确,那么它们应该可以正常工作。有时我会先执行模型验证,然后使用非常低的优先级进行视觉效果指令。 - ghiden
@ghiden 我找到了问题所在,我正在测试一个表单验证指令,并使用ng-model,我认为我的主要问题也是在使用名称作为指令以及输入元素上的属性。不过现在我已经解决了,并且正在进行测试!非常感谢你的示例,真的帮了我很多。 - thescientist
1
在调用$setViewValue之后,我添加了$scope.$digest()才使它对我起作用。我是做了一些奇怪的事情还是你的示例中缺少了这个?编辑:抱歉,我没有看到下面的答案。 - AndyPerlitch
我永远无法通过第二个测试(它总是为真)。我正在使用1.3.12版本,是否有什么变化? - Mocksy

2

我在自定义指令中测试,通过搜索"$error"对象的名称来进行自定义验证。例如:

  '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('eric+@eric.com').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('eric@eric.com').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)
  }));
});

我希望能够帮助您!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接