How to unit test simple property has validator set

2019-04-28 08:38发布

问题:

I have similar rules for some properties in multiple model objects and I want to replace them with custom property validators to avoid code duplication in unit tests.

I have my property validator:

public class IntIdPropertyValidator: PropertyValidator
{
    public IntIdPropertyValidator()
        : base("Property {PropertyName} should be greater than 0")
    {
    }

    protected override bool IsValid(PropertyValidatorContext context)
    {
        var value = (int)context.PropertyValue;

        return value > 0;
    }
}

And wiring it up in model validator class:

public class SomeRequestValidator : AbstractValidator<CreateWordRequest>
{
    public SomeRequestValidator()
    {
        RuleFor(x => x.Id).SetValidator(new IntIdPropertyValidator());
    }
}

Tried to test:

[Test]
public void Validate_IdHasValidator_Success()
{
    Init();

    validator.ShouldHaveChildValidator(x => x.Id, typeof(IntIdPropertyValidator));
}

But test always fails.

So, how can I test that validator is actually set for property Id?

回答1:

You are using ShouldHaveChildValidator in the wrong way. Id is a simple type.

ShouldHaveChildValidator is being in used on complex types. (see also the source code)

The right way to test the property is to pass valid objects and invalid objects and then varify using ShouldNotHaveValidationErrorFor and ShouldHaveValidationErrorFor:

[Test]
public void Should_have_error_when_Id_Is_Ilegal() {
      validator.ShouldHaveValidationErrorFor(p => p.Id, new CreateWordRequest());
}

[Test]
public void Should_not_have_error_when_Id_Is_Legal() {
      validator.ShouldNotHaveValidationErrorFor(p => p.Id, new CreateWordRequest()
                                                           {
                                                                Id = 7
                                                           });
}

Edit

The following code will do the verification you were looking for:

[Test]
public void Validate_IdHasValidator_Success()
{
    var validator = new SomeRequestValidator();

    var descriptor = validator.CreateDescriptor();
    var matchingValidators = descriptor.GetValidatorsForMember(
                Extensions.GetMember<CreateWordRequest, int>(x => x.Id).Name);

    Assert.That(matchingValidators.FirstOrDefault(), Is.InstanceOf<IntIdPropertyValidator>());

}

I'd like to explain you the reason that you shouldn't use the above code.

When you UT class you verify that the class behavior won't be harmed.

When you create a custom validator, you create a class with a responsibility to verify specific model( --> business rules)...

Id is a simple type with a business rules according to his parent model. Therefore you need to verify the business rules of Id through the model validator.

Let's assume that one of your models suddenly need to change. In this case you don't have any validation that any of you existing business rules won't harmed(or you decide to make changes inside IntIdPropertyValidator, such a move will affect anywhere, even if you didn't want to).

Creating a custom Property Validator is very good for code maintenance however, the tests should be against the model validator.

On complex types the story is quite different:

Usually complex types has their own business rules. In this case, you have to create a custom validator for them, and then verify that the parent validator use the right validator. Another thing to verify is: If the complex type is Null or complex rules such as "when the property value is X and then complex type state is Y"...