Javascript 为什么我的茉莉花测试不符合这个指令?

Javascript 为什么我的茉莉花测试不符合这个指令?,javascript,jquery,angularjs,jasmine,karma-jasmine,Javascript,Jquery,Angularjs,Jasmine,Karma Jasmine,我已经构建了一个angular指令onInputChange,当用户通过单击输入外部(模糊)或点击ENTER来更改输入值时,该指令应该会触发回调。该指令的使用方式如下: <input type="number" ng-model="model" on-input-change="callback()"/> 该指令的工作原理与我在应用程序中预期的一样。现在,我决定编写一些测试,以确保情况确实如此: describe("directive", function () { va

我已经构建了一个angular指令
onInputChange
,当用户通过单击输入外部(模糊)或点击
ENTER
来更改输入值时,该指令应该会触发回调。该指令的使用方式如下:

<input type="number" ng-model="model" on-input-change="callback()"/>
该指令的工作原理与我在应用程序中预期的一样。现在,我决定编写一些测试,以确保情况确实如此:

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

});
描述(“指令”,功能(){
var$compile、$rootScope、$scope、$element;
beforeach(函数(){
角度模拟模块(“app”);
});
每次之前(注入(函数($injector){
$compile=$injector.get(“$compile”);
$scope=$injector.get($rootScope”)。$new();
$scope.model=0;
$scope.onchange=函数(){
控制台日志(“调用”);
};
$element=$compile(“”)($scope);
$scope.$digest();
spyOn($scope,“onchange”);
}));
在每个(函数()之后){
$scope.$destroy();
});
它(“具有默认值”,函数(){
expect($scope.model).toBe(0);
expect($scope.onchange).not.toHaveBeenCalled();
});
它(“内部模型更改时不应触发回调”,函数(){
$scope.model=123;
$scope.$digest();
expect($scope.model).toBe(123);
expect($scope.onchange).not.toHaveBeenCalled();
});
//这失败了
它(“值未更改时不应触发回调”,函数(){
$element.focus();
$element.blur();
$scope.$digest();
expect($scope.model).toBe(0);
expect($scope.onchange).not.toHaveBeenCalled();
});
它(“当用户通过单击“离开”(模糊)更改输入时应触发回调)”,函数(){
$element.focus();
$element.val(456.change();
$element.blur();
$scope.$digest();
expect($scope.model).toBe(456);
expect($scope.onchange).toHaveBeenCalled();
});
//这失败了
它(“当用户通过单击enter更改输入时应触发回调”),函数(){
$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:

失败当用户通过单击enter更改输入时,指令应触发回调 预期已调用spy onchange。


我创造了一个你可以自己尝试的地方

1。为什么即使值没有更改,也会调用我的回调?

2。如何模拟用户在我的输入上点击
ENTER
?我已经尝试过不同的方法,但都不管用。

很抱歉问了这么长的问题。我希望我能提供足够的信息,这样也许有人能在这方面帮助我。谢谢:)


以下是关于我的问题的其他问题:


$parse
始终返回函数,并且
angular.isFunction(回调)
检查是不必要的

手动触发
keyup
时,
keyCode
不会转换为
哪个

$element.trigger($.Event("keyup", {which:13}))
也许会有帮助

之所以触发回调,是因为这里无法手动触发
focus
,而且它实际上是
未定义的!==
中的0
($(this).val()!==初始条件

focus
不起作用有几个原因。它不是即时的,规范应该是异步的。而且它在分离的元素上也不起作用

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

DOM测试属于功能测试,而不是单元测试,如果像这样对待jQuery,可能会带来很多惊喜(规范展示了冰山一角)。即使规范是绿色的,体内行为也可能与体外不同,这使得单元测试几乎毫无用处

对影响DOM的指令进行单元测试的正确策略是将所有事件处理程序公开给scope,或者在没有scope指令的情况下公开给controller:

require: ['onInputChange', 'ngModel'],
controller: function () {
  this.onFocus = () => ...;
  ...
},
link: (scope, element, attrs, [instance, ngModelController]) => { ... }
然后,控制器实例可以通过

var instance = $element.controller('onInputChange');
所有控制器方法都可以与相关事件分开进行测试。通过观察方法调用上的
,可以测试事件处理。为此,必须监视,如下所示:

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();
单元测试的目的是在与其他移动部件隔离的情况下测试单元(包括jquerydom操作,为此,
ngModel
也可以进行模拟),这就是测试的方式


单元测试不会使功能测试过时,特别是在复杂的多指令交互的情况下,但可能会提供100%覆盖率的可靠测试。

非常感谢您的深入解释。您回答的前半部分帮助我修复了测试,使所有测试都通过。无论如何,我现在正在修复我的测试方法正如你在下半部分所描述的。我是测试角度模块的新手,你的回答非常有帮助!+1请你看看这个更新的plunker:我不知道如何正确地应用和触发间谍。我已经用一些规范更新了你的plunker,以展示它是如何完成的。我已经更改了一些内容,以便于测试。隐私<代码>.
变量应该可以用于测试。并且*
上的
监听器直接提供给
监听器上的
,因此我们可以确保监听器是我们期望的函数(否则我们必须捕获匿名)
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();