Paul D'Ambra
Paul D'Ambra

Reputation: 7824

Using FluentValidator to validate children of properties

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.

Upvotes: 8

Views: 8583

Answers (1)

Electrionics
Electrionics

Reputation: 6782

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! ;-)

Upvotes: 8

Related Questions