简体   繁体   English

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

[英]Test that property has child validator FluentValidation

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.我有一个对象, instruction具有许多属性,其中大多数需要自己的验证器。 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 .当具有有效Property属性的 Instruction 对象传递给验证器时,验证器应为PropertyAdditionalInformation设置验证器。 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.但是我无法对此进行测试,因为无法将有效对象传递给 ShouldHaveChildValidator 方法,因此没有设置子验证器。

How do I design a test to check that these child validators are being set properly?我如何设计一个测试来检查这些子验证器是否被正确设置?

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.无论属性值如何,您的验证器都将始终被设置,这就是为什么您在调用ShouldHaveChildValidator方法时不必传递任何对象的实例。 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.所以我克隆了流畅的验证 git repo 并检查了代码如何检查子验证器的存在。

For this call:对于这次通话:

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

This is what is does:这就是它的作用:

  1. It gets the matching validators for the property expression you pass in to the method call: i => i.Property它获取您传递给方法调用的属性表达式的匹配验证器: i => i.Property
  2. It filters the matching validators to get only those of type IChildValidatorAdaptor .它过滤匹配的验证器以仅获取IChildValidatorAdaptor类型的验证器。
  3. It throws an error if none of the selected validators are assignable from the type you pass to the method call: FluentPropertyValidator如果选定的验证器都不能从您传递给方法调用的类型中分配,则会引发错误: 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.这就是DelegatingValidator类的情况,顺便说一下,它是您的子验证器使用的类型。 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 .由于我在命名事物时缺乏创造力(命名很难),我命名为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.此方法与代码中的方法相同,它也调用了我刚刚从 FluentValidation 的源中复制的其他几个方法,以便我可以添加小的修改。

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!希望这可以帮助!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM