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

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 上看到的关于我的问题的其他问题:

$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% 覆盖率的可靠测试。