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?
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"...