Проверьте, есть ли у свойства дочерний валидатор FluentValidation

Приведенный ниже тест терпит неудачу, когда я запускаю его. У меня есть объект instruction с рядом свойств, для большинства из которых требуются собственные валидаторы. Я хочу иметь возможность проверить наличие валидаторов для этих дочерних свойств, когда они установлены родительским валидатором.

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

В валидаторе для этого класса я определил следующие правила, которые гарантируют, что свойство в квесте имеет установленное значение, и устанавливает валидатор, когда свойство не равно нулю.

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

Когда объект Instruction с действительным свойством Property передается валидатору, валидатор должен установить валидаторы для Property и AdditionalInformation. Что и происходит, когда я запускаю свой код.

Однако я не могу проверить это, так как нет способа передать допустимый объект методу ShouldHaveChildValidator, и поэтому дочерний валидатор не устанавливается.

Как разработать тест для проверки правильности установки этих дочерних валидаторов?


person Community    schedule 18.10.2018    source источник


Ответы (1)


Я дам вам вариант, чтобы вы могли достичь того, чего хотите здесь, однако я должен сказать, что я не думал тщательно о каких-либо побочных эффектах, так что имейте это в виду.

Ваши валидаторы всегда будут установлены независимо от значений свойств, поэтому вам не нужно передавать экземпляр какого-либо объекта при вызове метода ShouldHaveChildValidator. Тот факт, что они будут казнены или нет, — это отдельная история, и, как вы знаете, это будет зависеть от ваших правил.

Итак, я клонировал репозиторий git с плавной проверкой и проверил, как код проверяет наличие дочерних валидаторов.

Для этого звонка:

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

Надеюсь это поможет!

person Karel Tamayo    schedule 18.10.2018