为什么我的茉莉花测试在这个指令上失败了?
Why does my jasmine tests fail on this directive?
我已经构建了一个 angular 指令 onInputChange
当用户通过点击输入外部(模糊)或点击 [=14= 来更改输入的值时应该触发回调].该指令可以像这样使用:
<input type="number" ng-model="model" on-input-change="callback()"/>
它使用以下代码:
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("<input type='number' ng-model='model' on-input-change='onchange()'>")($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();
});
});
现在,我的问题是我的两个测试在 运行 之后因业力失败:
A:
Failed directive should not fire callback when value has not changed
Expected spy onchange not to have been called.
B:
Failed directive should fire callback when user changes input by clicking enter
Expected spy onchange to have been called.
我创建了一个 Plunker,您可以自己尝试一下。
1.为什么即使值没有改变我的回调也会被调用?
2。我如何模拟用户在我的输入上点击 ENTER
?我已经尝试过不同的方法,但 none 有效。
抱歉这个问题太长了。我希望我能够提供足够的信息,以便有人可以帮助我解决这个问题。谢谢:)
我在 SO 上看到的关于我的问题的其他问题:
- How do I trigger a keyup/keydown event in an angularjs unit test?
- AngularJS unit test for keypress event
$parse
总是returns一个函数,angular.isFunction(callback)
检查是不必要的。
手动触发 keyup
时,keyCode
未转换为 which
。
$element.trigger($.Event("keyup", {which:13}))
可能会有帮助。
触发回调是因为这里focus
无法手动触发,实际是undefined !== 0
在($(this).val() !== initial
条件
有几个原因导致 focus
不起作用。它不是即时的,规范应该变成异步的。它不适用于分离元素。
focus
行为可以通过使用 $element.triggerHandler('focus')
而不是 $element.focus()
.
来修复
DOM测试属于功能测试,不属于单元测试,jQuery这样对待可能会带来很多惊喜(规范展示的只是冰山一角)。即使规格是绿色的,体内行为也可能与体外行为不同,这使得单元测试几乎毫无用处。
对影响 DOM 的指令进行单元测试的正确策略是将所有事件处理程序公开到范围 - 或控制器,在无范围指令的情况下:
require: ['onInputChange', 'ngModel'],
controller: function () {
this.onFocus = () => ...;
...
},
link: (scope, element, attrs, [instance, ngModelController]) => { ... }
然后controller实例可以通过
在specs中获取
var instance = $element.controller('onInputChange');
所有控制器方法都可以与相关事件分开测试。可以通过观察 on
方法调用来测试事件处理。为了做到这一点 必须被监视,就像那样:
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();
单元测试的目的是测试一个独立于其他活动部分的单元(包括jQuery DOM 动作,为此目的ngModel
也可以被模拟),那就是它是如何完成的。
单元测试不会使功能测试过时,尤其是在复杂的多方向交互的情况下,但可以提供 100% 覆盖率的可靠测试。
我已经构建了一个 angular 指令 onInputChange
当用户通过点击输入外部(模糊)或点击 [=14= 来更改输入的值时应该触发回调].该指令可以像这样使用:
<input type="number" ng-model="model" on-input-change="callback()"/>
它使用以下代码:
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("<input type='number' ng-model='model' on-input-change='onchange()'>")($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();
});
});
现在,我的问题是我的两个测试在 运行 之后因业力失败:
A:
Failed directive should not fire callback when value has not changed Expected spy onchange not to have been called.
B:
Failed directive should fire callback when user changes input by clicking enter Expected spy onchange to have been called.
我创建了一个 Plunker,您可以自己尝试一下。
1.为什么即使值没有改变我的回调也会被调用?
2。我如何模拟用户在我的输入上点击 ENTER
?我已经尝试过不同的方法,但 none 有效。
抱歉这个问题太长了。我希望我能够提供足够的信息,以便有人可以帮助我解决这个问题。谢谢:)
我在 SO 上看到的关于我的问题的其他问题:
- How do I trigger a keyup/keydown event in an angularjs unit test?
- AngularJS unit test for keypress event
$parse
总是returns一个函数,angular.isFunction(callback)
检查是不必要的。
keyup
时,keyCode
未转换为 which
。
$element.trigger($.Event("keyup", {which:13}))
可能会有帮助。
触发回调是因为这里focus
无法手动触发,实际是undefined !== 0
在($(this).val() !== initial
条件
有几个原因导致 focus
不起作用。它不是即时的,规范应该变成异步的。它不适用于分离元素。
focus
行为可以通过使用 $element.triggerHandler('focus')
而不是 $element.focus()
.
DOM测试属于功能测试,不属于单元测试,jQuery这样对待可能会带来很多惊喜(规范展示的只是冰山一角)。即使规格是绿色的,体内行为也可能与体外行为不同,这使得单元测试几乎毫无用处。
对影响 DOM 的指令进行单元测试的正确策略是将所有事件处理程序公开到范围 - 或控制器,在无范围指令的情况下:
require: ['onInputChange', 'ngModel'],
controller: function () {
this.onFocus = () => ...;
...
},
link: (scope, element, attrs, [instance, ngModelController]) => { ... }
然后controller实例可以通过
在specs中获取var instance = $element.controller('onInputChange');
所有控制器方法都可以与相关事件分开测试。可以通过观察 on
方法调用来测试事件处理。为了做到这一点
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();
单元测试的目的是测试一个独立于其他活动部分的单元(包括jQuery DOM 动作,为此目的ngModel
也可以被模拟),那就是它是如何完成的。
单元测试不会使功能测试过时,尤其是在复杂的多方向交互的情况下,但可以提供 100% 覆盖率的可靠测试。