Using FluentValidator to validate children of properties

不羁岁月 提交于 2019-12-23 09:29:31

问题


I want to use FluentValidation to validate some classes one of which is only used as a property on another... but I never directly create the child class so I want to test validation from the parent level. This may be unnecessary / crazy

So for example I have

public class Parent
{
  public string Text {get;set;}
  public Child Child {get;set;}
}

public class Child 
{
  public string Text {get;set;}
}

and

public class ParentValidator : AbstractValidator<Parent>
{
  public ParentValidator() 
  {
    RuleFor(p=>p.Text).NotEmpty();
  //RuleFor(p=>p.Child).SetValidator(new ChildValidator);
  //RuleFor(p=>p.Child.Text).NotEmpty();
  }
}

public class ChildValidator : AbstractValidator<Child>
{
  public ChildValidator() 
  {
    RuleFor(c=>c.Text).NotEmpty();
  }
}

which I test using

    [Test]
    public void ParentMustHaveText()
    {
        new ParentValidator()
             .ShouldHaveValidationErrorFor(p => p.Text, "");
    }
    [Test]
    public void ChildMustHaveText()
    {
        new ParentValidator().ShouldHaveValidationErrorFor(p => p.Child.Text, "");
    }

The ChildMustHaveText test always fails no matter how I set things up. Am I being crazy trying to test it that way?

since the following test always passes

    [Test]
    public void ChildMustHaveText()
    {
        new ChildValidator().ShouldHaveValidationErrorFor(c => c.Text, "");
    }

The classes are models in an ASP.NET WebApi Project.


回答1:


The first error is that you forget to specify creation of Child property object in default Parent constructor — FluentValidation try to set dynanically property of null.

public class Parent
{
    public Parent()
    {
        Child = new Child();
    }

    public string Text { get; set; }
    public Child Child { get; set; }
}

Notice that default constructor always uses in ShouldHaveValidationErrorFor for object creation before validation.

The next thing I found is that current implementation of ShouldHaveValidationErrorFor doesn't allow to check validity of nested properties with nesting level more than 1 (obj.Child1.Child2.Text is level 3 of nesting, for example).

PITFALL

Source code of buggy place (FluentValidation.TestHelper.ValidatorTester class):

public void ValidateError(T instanceToValidate) {
        accessor.Set(instanceToValidate, value);
        // 
        var count = validator.Validate(instanceToValidate, ruleSet: ruleSet).Errors.Count(x => x.PropertyName == accessor.Member.Name);

        if (count == 0) {
            throw new ValidationTestException(string.Format("Expected a validation error for property {0}", accessor.Member.Name));
        }
    }

EXPLANATION

Method compares joined property names with validation errors (x.PropertyName) with property object System.Reflection.RuntimePropertyInfo name (accessor.Member.Name), e.g. "Text" and "Child.Text" with "Text" for both tests, so test pass only because of parent.Text is null, it's not valid and property names equal to each other in both classes.

If simplify — now your test passes, but by wrong reason.

You can see this strange behavior if you rename one of string property:

public class Child 
{
    public string Text2 {get;set;}
}

or if you make Parent.Text property valid in tests (remove rule, or initialize in Parent() default constructor by not empty value).

public Parent()
{
    Child = new Child();
    Text = "I like pitfalls";
}

CONCLUSION

It's a bug in TestHelper class, and I hope this research helps you to decide on future test strategy for your application.

And never give up! ;-)



来源:https://stackoverflow.com/questions/27945523/using-fluentvalidator-to-validate-children-of-properties

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!