Reputation:
The below test fails when I run it. I have an object, instruction
that has a number of properties, most of which require their own validators. I want to be able to check that the validators for these child properties are present when set by the parent validator.
[Test]
public void ChildValidatorsSet()
{
_validator.ShouldHaveChildValidator(i => i.Property, typeof(FluentPropertyValidator));
_validator.ShouldHaveChildValidator(i => i.AdditionalInformation, typeof(FluentAdditionalInformationValidator));
}
Within the validator for this class I have the below rules defined that ensure the property in quest has a value set and sets a validator when the property is not null.
public FluentRemortgageInstructionValidator()
{
RuleFor(si => si.Property)
.NotNull().WithMessage("Solicitor Instruction: Instructions must have a linked property.");
RuleFor(si => si.Property)
.SetValidator(new FluentPropertyValidator()).When(si => si.Property != null);
RuleFor(si => si.AdditionalInformation)
.NotNull().WithMessage("Remortgage instructions must have an additional information table.");
RuleFor(si => si.AdditionalInformation)
.SetValidator(new FluentAdditionalInformationValidator()).When(si => si.AdditionalInformation != null);
}
Instruction class:
public class Instruction
{
[Key]
public AdditionalInformation AdditionalInformation { get; set; }
public Property Property { get; set; }
}
}
When an Instruction object with a valid Property
property is passed through to the validator the validator should then set validators for Property
and AdditionalInformation
. Which is what happens when I run my code.
However I am unable to test for this, as there is no way to pass a valid object through to the ShouldHaveChildValidator method, and therefore no child validator is being set.
How do I design a test to check that these child validators are being set properly?
Upvotes: 2
Views: 2244
Reputation: 3760
I'll give you an option so you can achieve what you want here, however I have to say that I haven't thought thoroughly about any side effects, so bear that in mind.
Your validators will always be set regardless of the property values, that's why you don't have to pass an instance of any object when calling ShouldHaveChildValidator
method. The fact that they get executed or not is another story, and that as you know will depend on your rulesets.
So I cloned the fluent validation git repo and checked out how does the code check for the existence of the child validators.
For this call:
_validator.ShouldHaveChildValidator(i=>i.Property, typeof(FluentPropertyValidator));
This is what is does:
i => i.Property
IChildValidatorAdaptor
. FluentPropertyValidator
It seems the code is missing the case where the validator is wrapped by another validator. That's the case of DelegatingValidator
class which, by the way, is the type your child validator uses. So one possible solution is to also take those validator types into consideration.
I created a extension method you can use following the same pattern of the original one. Due to my lack of creativity when naming things (naming is tough), I named ShouldHaveChildValidatorCustom
. This method is the same method in the code that also calls a couple of another methods that I just copied over from the sources of the FluentValidation so I could add the small modification.
Here is the complete extension class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using FluentValidation.Internal;
using FluentValidation.TestHelper;
using FluentValidation.Validators;
namespace YourTestExtensionsNamespace
{
public static class CustomValidationExtensions
{
public static void ShouldHaveChildValidatorCustom<T, TProperty>(this IValidator<T> validator, Expression<Func<T, TProperty>> expression, Type childValidatorType)
{
var descriptor = validator.CreateDescriptor();
var expressionMemberName = expression.GetMember()?.Name;
if (expressionMemberName == null && !expression.IsParameterExpression())
throw new NotSupportedException("ShouldHaveChildValidator can only be used for simple property expressions. It cannot be used for model-level rules or rules that contain anything other than a property reference.");
var matchingValidators = expression.IsParameterExpression() ? GetModelLevelValidators(descriptor) : descriptor.GetValidatorsForMember(expressionMemberName).ToArray();
matchingValidators = matchingValidators.Concat(GetDependentRules(expressionMemberName, expression, descriptor)).ToArray();
var childValidatorTypes = matchingValidators
.OfType<IChildValidatorAdaptor>()
.Select(x => x.ValidatorType);
//get also the validator types for the child IDelegatingValidators
var delegatingValidatorTypes = matchingValidators
.OfType<IDelegatingValidator>()
.Where(x => x.InnerValidator is IChildValidatorAdaptor)
.Select(x => (IChildValidatorAdaptor)x.InnerValidator)
.Select(x => x.ValidatorType);
childValidatorTypes = childValidatorTypes.Concat(delegatingValidatorTypes);
var validatorTypes = childValidatorTypes as Type[] ?? childValidatorTypes.ToArray();
if (validatorTypes.All(x => !childValidatorType.GetTypeInfo().IsAssignableFrom(x.GetTypeInfo())))
{
var childValidatorNames = validatorTypes.Any() ? string.Join(", ", validatorTypes.Select(x => x.Name)) : "none";
throw new ValidationTestException(string.Format("Expected property '{0}' to have a child validator of type '{1}.'. Instead found '{2}'", expressionMemberName, childValidatorType.Name, childValidatorNames));
}
}
private static IPropertyValidator[] GetModelLevelValidators(IValidatorDescriptor descriptor)
{
var rules = descriptor.GetRulesForMember(null).OfType<PropertyRule>();
return rules.Where(x => x.Expression.IsParameterExpression()).SelectMany(x => x.Validators)
.ToArray();
}
private static IEnumerable<IPropertyValidator> GetDependentRules<T, TProperty>(string expressionMemberName, Expression<Func<T, TProperty>> expression, IValidatorDescriptor descriptor)
{
var member = expression.IsParameterExpression() ? null : expressionMemberName;
var rules = descriptor.GetRulesForMember(member).OfType<PropertyRule>().SelectMany(x => x.DependentRules)
.SelectMany(x => x.Validators);
return rules;
}
}
}
And this is a test that should pass if you set the child validators to your classes and fail otherwise:
[Fact]
public void ChildValidatorsSet()
{
var _validator = new FluentRemortgageInstructionValidator();
_validator.ShouldHaveChildValidatorCustom(i => i.Property, typeof(FluentPropertyValidator));
_validator.ShouldHaveChildValidatorCustom(i => i.AdditionalInformation, typeof(FluentAdditionalInformationValidator));
}
Hope this helps!
Upvotes: 1