为什么我的茉莉花测试在这个指令上失败了? [英] Why does my jasmine tests fail on this directive?

查看:104
本文介绍了为什么我的茉莉花测试在这个指令上失败了?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have built an angular directive onInputChange that should fire a callback when the users changes a value of an input by either clicking outside of the input (blur) or hitting ENTER. The directive can be used like:

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

该指令的工作原理与我在我的应用中的预期相同。现在我决定编写一些测试以确保实现这种情况:

The directive works as I would expect in my app. Now I've decided to write some tests to really ensure this is the case:

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

});

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

Now, my problem is that two of my tests are failing after run with karma:

A:


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

Failed directive should not fire callback when value has not changed Expected spy onchange not to have been called.

B:


失败指令应该在用户通过点击输入更改输入时触发回调
已调用预期的间谍on on。






我创建了一个 Plunker 你可以自己试试。

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

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

2. How can I simulate the user hitting ENTER on my input? I already tried different ways but none works.

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

Sorry for the long question. I hope I was able to provide enough information so that maybe someone can help me out on this. Thank you :)

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

Other questions here on SO that I've read regarding my issue:

  • How do I trigger a keyup/keydown event in an angularjs unit test?
  • AngularJS unit test for keypress event

推荐答案

$ parse 总是返回一个函数, angular。 isFunction(回调)检查是不必要的。

$parse always returns a function, and angular.isFunction(callback) check is unnecessary.

keyCode 未转换为<$当手动触发 keyup 时,c $ c>哪个。

keyCode is not translated to which when triggering keyup manually.

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

可能有所帮助。

由于焦点可以触发回调在这里手动触发,实际上 undefined!== 0 in ($(this).val()!== initial 条件。

The callback is triggered because focus can't be triggered manually here, and it is actually undefined !== 0 in ($(this).val() !== initial condition.

焦点有几个原因无效。它不是即时的,规范应该变得异步。并且它不适用于分离元素。

There are a couple of reason for focus to not work. It isn't instant, and the spec should become asynchronous. And it won't work on detached element.

焦点可以使用 $ element.triggerHandler('focus')而不是 $ element.focus()

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

DOM testing belongs to functional tests, not to unit tests, and jQuery may introduce a lot of surprises when being treated like that (the spec demonstrates the tip of the iceberg). Even when the specs are green, in vivo behaviour may differ from in vitro, this renders unit tests almost useless.

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

A proper strategy for unit-testing a directive that affects DOM is to expose all event handlers to scope - or to controller, in the case of no-scope directive:

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

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

Then controller instance can be obtained in specs with

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

所有控制器方法都可以与相关事件分开测试。并且可以通过在方法调用上观察来测试事件处理。为了做到这一点, angular.element.prototype jQuery.prototype 必须被监视,例如:

All controller methods can be tested separately from the relevant events. And events handling can be tested by watching for on method calls. In order to do this angular.element.prototype or jQuery.prototype has to be spied, like that:

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 也可以被嘲笑),就是这样做的。

The purpose of unit test is to test a unit in isolation from other moving parts (including jQuery DOM actions, for this purpose ngModel can be mocked too), that's how it is done.

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

Unit tests don't make functional tests obsolete, especially in the case of complex multidirective interactions but may offer solid testing with 100% coverage.

这篇关于为什么我的茉莉花测试在这个指令上失败了?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆