【问题标题】:Test that property has child validator FluentValidation测试该属性是否具有子验证器 FluentValidation
【发布时间】:2018-10-18 13:01:58
【问题描述】:

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

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

在此类的验证器中,我定义了以下规则,以确保 quest 中的属性具有值集,并在属性不为空时设置验证器。

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 属性的指令对象传递给验证器时,验证器应为Property 和AdditionalInformation 设置验证器。当我运行我的代码时会发生什么。

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

如何设计测试来检查这些子验证器是否设置正确?

【问题讨论】:

    标签: c# fluentvalidation


    【解决方案1】:

    我会给你一个选择,这样你就可以在这里实现你想要的,但是我不得不说我没有彻底考虑过任何副作用,所以请记住这一点。

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

    所以我克隆了 fluent validation 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));
    }
    

    希望这会有所帮助!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-04-27
      • 2019-09-17
      • 1970-01-01
      • 2013-12-19
      • 2011-06-24
      • 2015-09-20
      • 1970-01-01
      相关资源
      最近更新 更多