2017-10-05 41 views
0

我正在使用伟大的FluentValidation库,并在单元测试验证程序中遇到问题。我有以下的例子此Wiki网页上尽可能接近(但我使用的xUnit)https://github.com/JeremySkinner/FluentValidation/wiki/g.-TestingFluent验证测试未处理预期空值

它使用ShouldHaveValidationErrorFor扩展方法测试。

我得到一个NullReferenceException与测试失败。但那正是我所测试的 - 一个必填字段的空引用。

这里是我的代码:

验证:

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."); 
    } 
} 

测试:

public class ChangeEmailRequestInputModelValidatorTests 
{ 
    [Fact] 
    public void Errors_Where_NewEmail_Is_Null() 
    { 
     var val = new ChangeEmailRequestInputModelValidator(); 

     val.ShouldHaveValidationErrorFor(v => v.NewEmail, null as string);    
    } 
} 

任何想法,为什么发生这种情况?我一定有什么不对,但看起来和我很模糊的例子很相似。

回答

1

你有三个规则设置:

  1. NEWEMAIL不能为空
  2. 如果NEWEMAIL不为空,修剪,并确保它是一个有效的电子邮件地址
  3. 修剪NewEmailConfirm,确保它不是空,将其与修剪过的NewEmail进行比较并确保其匹配。

这是导致问题的第三条规则。 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 = "[email protected]" }); 
} 

这将实例类,下验证与正确的位上,首先表达不炸毁。你现在要面对的问题是,在某些时候你打到.Equal(m => m.NewEmail.Trim())。您的灯具明确将此设置为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(); 
} 

以上仍设法同时验证您的性质,相互独立的。然后它只是相互检查两个属性,利用空合并运算符来避开显式的空检查。

+0

谢谢。这是完全合理的。我知道问题出在我的代码上。只是看不到它。 – onefootswill

+0

对不起,重新审视这一点,但不应该当条件保护代码当值为null时炸掉。我预计Trim将不会被称为“何时”会阻止代码的执行。 – onefootswill

+0

你是对的,你能不接受我的回答,我会用*真实的*它更新它的原因吗? –