FluentValidation测试未处理预期的空值

时间:2017-10-05 22:31:03

标签: unit-testing fluentvalidation

我使用了很棒的FluentValidation库,并在单元测试验证器时遇到了问题。我在这个维基页面上尽可能地遵循了这个例子(但我使用的是xUnit)https://github.com/JeremySkinner/FluentValidation/wiki/g.-Testing

使用 ShouldHaveValidationErrorFor 扩展方法的测试。

我收到 NullReferenceException 且测试失败。但这正是我正在测试的 - 对于必填字段的空引用。

这是我的代码:

验证者:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://rawgit.com/Semantic-Org/Semantic-UI/next/dist/semantic.js"></script>
<link href="https://semantic-ui.com/dist/semantic.css" rel="stylesheet"/>

<form class="ui form">
  <h4 class="ui header">Semantic UI multiple dropdowns, only allow value to be selected once</h4>
  <div class="field">
    <div class="label">Select a value here:</div>
    <div id="dd1" class="ui selection dropdown">
      <input type="hidden" name="group[logic][1][0]" value="john_doe" data-validate="dropdown_input">
      <div class="text"></div>
      <i class="dropdown icon"></i>
    </div>
  </div>
  <div class="field">
    <div class="label">You should not be able to select the value above, in any of these below:</div>
    <div id="dd2" class="ui selection dropdown">
      <input type="hidden" name="group[logic][1][1]" value="john_doe" data-validate="dropdown_input">
      <div class="text"></div>
    </div>
  </div>
  <div class="field">
    <div class="label">Has Value and No Selected Set</div>
    <div id="dd3" class="ui selection dropdown">
      <input type="hidden" name="group[logic][1][2]" value="john_doe" data-validate="dropdown_input">
      <div class="text"></div>
    </div>
  </div>
</form>

测试:

public class ChangeEmailRequestInputModelValidator : AbstractValidator<ChangeEmailRequestInputModel>
{
    public ChangeEmailRequestInputModelValidator()
    {
        RuleFor(x => x.NewEmail)
            .NotEmpty(); 

        RuleFor(m => m.NewEmail.Trim())
            .EmailAddress()
            .When(m => m.NewEmail != null)
            .WithMessage(ValidationConstants.SymbolIsNotAValidEmailAddress, x => x.NewEmail)
            .WithName("NewEmail"); 

        RuleFor(m => m.NewEmailConfirm.Trim())
            .Cascade(CascadeMode.Continue)
            .NotEmpty().WithMessage("Confirm New Email field cannot be empty.")
            .Equal(m => m.NewEmail.Trim()).WithMessage("Confirm New Email field must be equal to the New Email.");
    }
}

知道为什么会这样吗?我一定有些不对劲,但它看起来与我感到困惑的例子非常相似。

1 个答案:

答案 0 :(得分:2)

您设置了三个规则:

  1. NewEmail不能为空
  2. 如果NewEmail不为null,请修剪它并确保它是有效的电子邮件地址
  3. 修剪NewEmailConfirm,确保它不为空,将其与修剪后的NewEmail进行比较并确保匹配。
  4. 这是导致问题的第三条规则。 FluentValidation的工作方式意味着要评估的第一个表达式是传递给RuleFor的表达式,除非您使用WhenUnless,这两个表达式都会回溯规则并应用谓词或反向谓词传递给那些条件方法。

    所以从本质上讲,你的验证器会启动,它会通过3个规则中的2个,然后开始评估第三个规则。你的测试夹具没有为NewEmailConfirm设置一个值,所以它开始评估表达式链并点击第一个表达式m => m.NewEmailConfirm.Trim(),然后它会爆炸。

    您可以采取以下措施来防止这种情况:

    [Fact]
    public void Errors_Where_NewEmail_Is_Null()
    {
        var sut = new ChangeEmailRequestInputModelValidator();
        sut.ShouldHaveValidationErrorFor(v => v.NewEmail, new ChangeEmailRequestInputModel { NewEmail = null, NewEmailConfirm = "demo.email@example.com" });
    }
    

    这将使用正确的位实例化您的类验证不会破坏第一个表达式。您现在要面对的问题是,您可能会在某个时刻点击.Equal(m => m.NewEmail.Trim())。你的fixture明确地将它设置为null以测试规则2,因此规则3仍然需要重新构建。

    我建议如下:

    public ChangeEmailRequestInputModelValidator(){
    
        RuleFor(m => m.NewEmail)
            .Cascade(CascadeMode.StopOnFirstFailure)
            .NotEmpty()
            .WithMessage("Email is a required field.")
            .EmailAddress()
            .WithMessage(
                ValidationConstants.SymbolIsNotAValidEmailAddress, x => x.NewEmail)
            .WithName("NewEmail");
    
        RuleFor(m => m.NewEmailConfirm)
            .NotEmpty()
            .WithMessage("Confirm New Email field cannot be empty.");
    
        RuleFor(m => m)
            .Must(HaveMatchingEmailAndConfirmEmail)
            .WithMessage("Confirm New Email field must be equal to the New Email.");
    }
    
    private bool HaveMatchingEmailAndConfirmEmail(ChangeEmailRequestInputModel model)
    {
        return model.NewEmail?.Trim() == model.NewEmailConfirm?.Trim();
    }
    

    上述内容仍设法验证您的属性,彼此独立。然后它只是相互检查两个属性,利用null-coalescing运算符来绕过显式的空值检查。