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.
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! ;-)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With