测试该属性是否具有子验证程序FluentValidation

时间:2018-10-18 13:01:58

标签: c# fluentvalidation

以下测试在我运行时失败。我有一个对象instruction,它具有许多属性,其中大多数需要自己的验证器。我希望能够检查由父验证器设置的这些子属性的验证器是否存在。

[Test]
public void ChildValidatorsSet()
{
    _validator.ShouldHaveChildValidator(i => i.Property, typeof(FluentPropertyValidator));
    _validator.ShouldHaveChildValidator(i => i.AdditionalInformation, typeof(FluentAdditionalInformationValidator));
}

在此类的验证器中,我定义了以下规则,以确保quest中的属性具有值集,并在该属性不为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);
}

说明课:

public class Instruction
{
    [Key]
    public AdditionalInformation AdditionalInformation { get; set; }
    public Property Property { get; set; }
    }
}

当具有有效Property属性的指令对象传递给验证器时,验证器应随后为PropertyAdditionalInformation设置验证器。当我运行代码时会发生什么。

但是我无法对此进行测试,因为无法将有效对象传递给ShouldHaveChildValidator方法,因此没有设置任何子验证器。

如何设计测试以检查这些子验证器的设置正确?

1 个答案:

答案 0 :(得分:0)

我会给您一个选择,以便您可以在这里达到想要的目标,但是我必须说,我还没有彻底考虑任何副作用,所以请记住这一点。

无论属性值如何,都将始终设置验证器,这就是为什么在调用ShouldHaveChildValidator方法时不必传递任何对象的实例的原因。它们是否被执行的事实是另外一回事,而您所知道的将取决于您的规则集。

因此,我克隆了流畅的验证git repo,并检查了代码如何检查子验证器的存在。

为此通话:

_validator.ShouldHaveChildValidator(i=>i.Property, typeof(FluentPropertyValidator));

这是做什么的

  1. 它为您传递的属性表达式获取匹配的验证器 进入方法调用:i => i.Property
  2. 它过滤匹配的验证器,以仅获取类型为IChildValidatorAdaptor的验证器。
  3. 如果没有选定的验证器可以通过传递给方法调用的类型来分配,则抛出错误:FluentPropertyValidator

在验证器由另一个验证器包装的情况下,似乎缺少代码。 DelegatingValidator类就是这种情况,顺便说一句,它是您的孩子验证器使用的类型。因此,一种可能的解决方案是也考虑那些验证器类型。

我创建了一种扩展方法,可以使用与原始方法相同的模式。由于命名时我缺乏创造力(命名很困难),因此我命名为ShouldHaveChildValidatorCustom。此方法与代码中的方法相同,它还调用了我刚刚从FluentValidation的源复制过来的其他两个方法,因此可以添加小的修改。

这是完整的扩展程序类:

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;
        }
    }
}

如果您将子验证器设置为类,否则此测试将通过,否则将失败:

[Fact]
public void ChildValidatorsSet()
{
    var _validator = new FluentRemortgageInstructionValidator();

    _validator.ShouldHaveChildValidatorCustom(i => i.Property, typeof(FluentPropertyValidator));
    _validator.ShouldHaveChildValidatorCustom(i => i.AdditionalInformation, typeof(FluentAdditionalInformationValidator));
}

希望这会有所帮助!