这是否是进行角度表单验证的正确方法?

时间:2014-03-31 15:29:34

标签: angularjs validation unit-testing

我正在尝试学习angularjs,目前正在查看表单和表单验证。通过跟踪角度站点上的文档,我有一个模板html文件,如下所示:

<form role="form" name="fnolForm" novalidate autocomplete="off" data-ng-submit="submit()">
    <fieldset class="form-group">
        <legend>Information about you</legend>
        <label data-ng-class="{'has-error' : fnolForm.submitError && fnolForm.name.$invalid}">
            Your name
            <input class="form-control" type="text" data-ng-model="fnol.insuredParty.name" name="name" required/>
            <span class="error" data-ng-show="fnolForm.submitError && fnolForm.name.$error.required">Please provide your name</span>
        </label>
        <label data-ng-class="{'has-error' : fnolForm.submitError && fnolForm.phoneNumber.$invalid}">
            Contact number
            <input class="form-control" type="tel" data-ng-model="fnol.insuredParty.phoneNumber" name="phoneNumber" required/>
            <span class="error" data-ng-show="fnolForm.submitError && fnolForm.phoneNumber.$error.required">Please provide your contact number</span>
        </label>
        <label data-ng-class="{'has-error' : fnolForm.email.$error.email || (fnolForm.submitError && fnolForm.email.$invalid)}">
            Email address
            <input class="form-control" type="email" data-ng-model="fnol.insuredParty.email" name="email" required/>
            <span class="error" data-ng-show="fnolForm.submitError && fnolForm.email.$error.required">Please provide your email address</span>
            <span class="error" data-ng-show="fnolForm.email.$error.email">Please enter a valid email address</span>
        </label>
    </fieldset>

在我的控制器中我正在做:

$scope.submit = function() {
    if ($scope.fnolForm.$valid) {
        // client side validation has passed, do something ....
    } else {
        // client side validation has failed
        $scope.fnolForm.submitError = true;
    }
};

虽然我的验证工作正常,但它在许多方面感觉不对:

  • 业务逻辑(验证规则)与视图关注点混淆
  • 验证规则(显然)不可测试
  • 客户端验证易于工作/黑客攻击 - 需要通过服务器端验证进行备份

我确定有最后一点的解决方案(验证规则由服务器端验证备份);但前两个让我担心

这是否真的是以角度进行客户端验证的正确方法?我们真的必须在模板中编写规则吗?如果我们这样做,我们究竟应该如何对验证规则进行单元测试?

对此有任何意见(没有双关语:))

干杯

编辑31/03/14 19:50

阅读完评论并重新阅读我已粘贴的代码后,我认为我原本很难在视图中接受fnolForm.submitError && fnolForm.name.$invalid类型逻辑。
但是,经过反思,我现在可以看到,这实际上不是确定表单验证的业务逻辑,它纯粹是表示糖。 IE浏览器。我们可以删除所有这些,表单仍然具有相同的验证规则:

<form role="form" name="fnolForm" novalidate autocomplete="off" data-ng-submit="submit()">
    <fieldset class="form-group">
        <legend>Information about you</legend>
        <label>
            Your name
            <input class="form-control" type="text" data-ng-model="fnol.insuredParty.name" name="name" required/>
        </label>
        <label>
            Contact number
            <input class="form-control" type="tel" data-ng-model="fnol.insuredParty.phoneNumber" name="phoneNumber" required/>
        </label>
        <label>
            Email address
            <input class="form-control" type="email" data-ng-model="fnol.insuredParty.email" name="email" required/>
        </label>
    </fieldset>

在上述情况下,我的表单验证规则由标准html5属性(requiredtype="email"等)确定,其中angularjs提供了跨浏览器逻辑,因此我不# 39; t需要进行单元测试。我知道angularjs为混合/最大长度等(指令)提供了自定义的附加字段验证器,所以我也没有测试它们。

但我仍然无法帮助我认为我需要将我的表单作为一个单元进行测试,特别是它是否有效。
例如,域业务规则可能会说只有在填写完姓名,电话号码和电子邮件字段,并且电子邮件地址字段包含有效(格式化)的电子邮件地址时才能提交表单。
在这种情况下,我想编写一系列单元测试,我用各种数据组合填充表单对象属性,并断言表单的有效性。

也许问题是我来自java背景,上面的模式是:单元测试肯定是我以前所使用的。

4 个答案:

答案 0 :(得分:1)

好的,在考虑了所提供的各种选项,评论和答案后(感谢大家),我已经找到了一个基于编写验证服务的解决方案,并将其注入控制器。
这对我来说似乎是最重要的方框,因为这意味着我对什么构成有效形式及其可测试形式有明确的定义。但另外它还使用angularjs指令等来装饰表单(根据我原来的标记) - 演示糖:)

所以,这是我的最终解决方案。这是我以前的视图模板:

<form role="form" name="fnolForm" novalidate autocomplete="off" data-ng-submit="submit()">
    <fieldset class="form-group">
        <legend>Information about you</legend>
        <label data-ng-class="{'has-error' : fnolForm.submitError && fnolForm.name.$invalid}">
            Your name
            <input class="form-control" type="text" data-ng-model="fnol.insuredParty.name" name="name" required/>
            <span class="error" data-ng-show="fnolForm.submitError && fnolForm.name.$error.required">Please provide your name</span>
        </label>
        <label data-ng-class="{'has-error' : fnolForm.submitError && fnolForm.phoneNumber.$invalid}">
            Contact number
            <input class="form-control" type="tel" data-ng-model="fnol.insuredParty.phoneNumber" name="phoneNumber" required/>
            <span class="error" data-ng-show="fnolForm.submitError && fnolForm.phoneNumber.$error.required">Please provide your contact number</span>
        </label>
        <label data-ng-class="{'has-error' : fnolForm.email.$error.email || (fnolForm.submitError && fnolForm.email.$invalid)}">
            Email address
            <input class="form-control" type="email" data-ng-model="fnol.insuredParty.email" name="email" required/>
            <span class="error" data-ng-show="fnolForm.submitError && fnolForm.email.$error.required">Please provide your email address</span>
            <span class="error" data-ng-show="fnolForm.email.$error.email">Please enter a valid email address</span>
        </label>
    </fieldset>

我写了一项服务如下:

fnolService.factory("FnolFormValidator",
    [
        function() {
            return function(fnol) {
                return !!(fnol &&
                    fnol.insuredParty &&
                    fnol.insuredParty.name &&
                    fnol.insuredParty.phoneNumber &&
                    fnol.insuredParty.email &&
                    fnol.insuredParty.email.match(/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i) !== null);
            }
        }
    ]
);

我将它注入我的控制器并按如下方式使用:

fnolControllers.controller("ReportAccidentController",
    [
        "$scope",
        "$modal",
        "fnol",
        "FnolFormValidator",
        function($scope, $modal, fnol, FnolFormValidator) {

            $scope.submit = function() {
                if ($scope.fnolForm.$valid && FnolFormValidator($scope.fnol)) {
                    // client side validation has passed, do something ....
                } else {
                    // client side validation has failed
                   $scope.fnolForm.submitError = true;
                }
            };

我非常喜欢这个解决方案,因为它允许我单元测试表单的验证规则(即FnolFormValidator服务)以及控制器逻辑。如果我想测试演示糖,那么我可以用Selenium或类似的东西写一些东西 对我来说,这感觉就像是关注点的正确分离。

答案 1 :(得分:0)

我想这就是你如何看待它。我理解关于业务逻辑的关注点。我想我看到我不会那样看。它只是向用户显示反馈。发生这种情况的逻辑实际上位于表单对象中。所以你只是调用表达式来在其他地方进行评估。

我个人喜欢在视图中保留它。它感觉它属于那里(虽然它确实开始使简单的形式感觉有点膨胀)。但是如果你相反,你可以在控制器中实现它。 $scope.fnolForm.submitError等。但是,如果您想要实时反馈,则需要在ng-change()上加input来模仿它。然后设置一个标记,让您的ng-show/hide进行评估。

答案 2 :(得分:0)

为了回答你的第一点,它确实与表单验证有点混淆。 但是,设置这些错误值的所有业务逻辑都在视图后面处理,视图只是查看它们或它们的组合来决定如何显示错误。

但是视图中有很多代码,所以我的建议是使用指令作为输入。请参阅http://docs.angularjs.org/guide/directive

指令可以将您的输入包装到1&#39;元素中。你可以传递重要的部分。该指令处理错误的所有代码和逻辑,因此您不需要在视图中直接执行此操作。您只需要一个表单元素作为指令的父元素,然后您可以将控制器作为指令链接函数的第4个参数拉出来。关于指令的文档很广泛,将涵盖所有这些。

希望有所帮助!

答案 3 :(得分:0)

这也是我在做客户端验证的方式。让我试着回答你的问题。

  1. 业务逻辑(验证规则与视图问题混淆)

    • 如果您还在进行服务器端验证,实际上角度表单验证不会干扰业务逻辑,但他们只是决定如何向用户显示错误或警告,这完全是前端。
  2. 验证规则(显然)不可测试

    • 您不可能通过单元测试来测试这些验证规则,但您绝对可以使用e2e或使用selenium驱动程序进行功能测试来测试它们。我认为用e2e测试测试前端的东西会更好。
  3. 正如您所注意到的,即使您有这些不错的角度式客户端验证,也应始终使用服务器端验证。