为什么我的茉莉花测试在这个指令上失败了?

我已经构建了一个角度指令onInputChange ,当用户通过单击输入外部(模糊)或按ENTER来更改输入值时,该指令应触发回调。 该指令可以像:

  

它使用以下代码:

 app.directive('onInputChange', [ "$parse", function ($parse) { return { restrict : "A", require : "ngModel", link : function ($scope, $element, $attrs) { // var dirName = "onInputChange", callback = $parse($attrs[dirName]), evtNS = "." + dirName, initial = undefined; // if (angular.isFunction(callback)) { $element .on("focus" + evtNS, function () { initial = $(this).val(); }) .on("blur" + evtNS, function () { if ($(this).val() !== initial) { $scope.$apply(function () { callback($scope); }); } }) .on("keyup" + evtNS, function ($evt) { if ($evt.which === 13) { $(this).blur(); } }); } // $scope.$on("$destroy", function () { $element.off(evtNS); }); } }; } ]); 

该指令的工作方式与我在应用程序中的预期相同。 现在我决定编写一些测试来确保这种情况:

 describe("directive", function () { var $compile, $rootScope, $scope, $element; beforeEach(function () { angular.mock.module("app"); }); beforeEach(inject(function ($injector) { $compile = $injector.get("$compile"); $scope = $injector.get("$rootScope").$new(); $scope.model = 0; $scope.onchange = function () { console.log("called"); }; $element = $compile("")($scope); $scope.$digest(); spyOn($scope, "onchange"); })); afterEach(function () { $scope.$destroy(); }); it("has default values", function () { expect($scope.model).toBe(0); expect($scope.onchange).not.toHaveBeenCalled(); }); it("should not fire callback on internal model change", function() { $scope.model = 123; $scope.$digest(); expect($scope.model).toBe(123); expect($scope.onchange).not.toHaveBeenCalled(); }); //this fails it("should not fire callback when value has not changed", function () { $element.focus(); $element.blur(); $scope.$digest(); expect($scope.model).toBe(0); expect($scope.onchange).not.toHaveBeenCalled(); }); it("should fire callback when user changes input by clicking away (blur)", function () { $element.focus(); $element.val(456).change(); $element.blur(); $scope.$digest(); expect($scope.model).toBe(456); expect($scope.onchange).toHaveBeenCalled(); }); //this fails it("should fire callback when user changes input by clicking enter", function () { $element.focus(); $element.val(789).change(); $element.trigger($.Event("keyup", {keyCode:13})); $scope.$digest(); expect($scope.model).toBe(789); expect($scope.onchange).toHaveBeenCalled(); }); }); 

现在,我的问题是我的两个测试在使用karma运行后失败了:

A:

当值没有改变时, 失败的指令不应该触发回调 预期的spy onchange没有被调用。

B:

失败的指令应该在用户通过单击“输入 预期的间谍”来 更改输入时触发回调


我已经创建了一个Plunker ,您可以自己尝试一下。

1.为什么即使值没有改变,我的回调也会被调用?

2.如何模拟用户在我的输入上按ENTER 我已经尝试了不同的方法,但都没有。

抱歉,这个问题很长。 我希望我能够提供足够的信息,以便有人可以帮助我解决这个问题。 谢谢 :)


关于我的问题我在这里读到的其他问题:

  • 如何在angularjsunit testing中触发keyup / keydown事件?
  • 针对按键事件的AngularJSunit testing

$parse总是返回一个函数,并且angular.isFunction(callback)检查。

keyCode未转换为手动触发keyup

 $element.trigger($.Event("keyup", {which:13})) 

可能有帮助。

回调被触发,因为这里不能手动触发focus ,实际上它是undefined !== 0 ($(this).val() !== initial条件。

有几个原因需要focus不起作用。 它不是即时的,规范应该变得异步。 并且它不适用于分离元素。

focus行为可以通过使用$element.triggerHandler('focus')而不是$element.focus()来修复。

DOM测试属于function测试,而不属于unit testing,jQuery在被这样对待时会引入很多惊喜(规范certificate了冰山一角)。 即使规格是绿色的,体内行为可能与体外不同,这使得unit testing几乎无用。

对影响DOM的指令进行unit testing的正确策略是在无范围指令的情况下将所有事件处理程序公开到范围 – 或控制器:

 require: ['onInputChange', 'ngModel'], controller: function () { this.onFocus = () => ...; ... }, link: (scope, element, attrs, [instance, ngModelController]) => { ... } 

然后可以在规格中获得控制器实例

 var instance = $element.controller('onInputChange'); 

所有控制器方法都可以与相关事件分开测试。 并且可以通过观察方法调用来测试事件处理。 为了做到这一点, angular.element.prototypejQuery.prototype ,如下所示:

 spyOn(angular.element.prototype, 'on').and.callThrough(); spyOn(angular.element.prototype, 'off').and.callThrough(); spyOn(angular.element.prototype, 'val').and.callThrough(); ... $element = $compile(...)($scope); expect($element.on).toHaveBeenCalledWith('focus.onInputChange', instance.onFocus); ... instance.onFocus(); expect($element.val).toHaveBeenCalled(); 

unit testing的目的是ngModel测试一个单元与其他移动部件(包括jQuery DOM动作,为此目的也可以ngModel ),就是这样做的。

unit testing不会使function测试过时,特别是在复杂的多指向交互的情况下,但可以提供100%覆盖率的可靠测试。