FluentValidation.Validators.UnitTestExtension
FluentValidation.Validators.UnitTestExtension copied to clipboard
Issue #14
Resolve https://github.com/MichalJankowskii/FluentValidation.Validators.UnitTestExtension/issues/14
The issue was particularly prevalent when working with nullable types as most of the time validator rules have .Value at the end. Something like this:
RuleFor(x => x.NullableInt.Value)
.GreaterThan(0)
.When(x => x.NullableInt.HasValue);
RuleFor(x => x.OtherNullableInt.Value)
.GreaterThan(0)
.When(x => x.OtherNullableInt.HasValue);
The problem was that ShouldHaveRules method only considers the immediate Members of all the rules. However, this doesn't work when one tries to do something like this:
RuleFor(x => x.Driver.Id)
.GreaterThan(0)
.When(x => x.Driver != null);
RuleFor(x => x.Assistant.Id)
.GreaterThan(0)
.When(x => x.Assistant != null);
As then only Id part gets considered and it seems like x.Driver.Id and x.Assistant.Id are the same thing as they have the same names and same types.
To fix this I propose to recursively compare the expressions. This way we go all the way down and we can then notice that actually the Driver and Assistant parts are different types with different names.
Nice. I will look at in during the weekend and provide a package update.
I didn't realise earlier that this is a much deeper issue. This fix only works for couple of cases as I cover only MemberAccess expression type. The issue is that I don't exhaustively compare expression trees in the CompareMembersRecursively method.
Cases like this will currently fail:
public class Model
{
public int[] Numbers { get; set; }
}
public class ModelValidator : AbstractValidator<Model>
{
public ModelValidator()
{
RuleForEach(model => model.Numbers.Select(number => number + 1))
.GreaterThan(0);
}
}
public class ModelValidatorTests
{
[Fact]
public void ModelValidatorHasCorrectRules()
{
var validator = new ModelValidator();
validator.ShouldHaveRules(x => x.Numbers.Select(number => number + 1),
new ComparisonValidatorVerifier<GreaterThanValidator>(0));
}
}
One way to solve this issue is to develop a way to compare expression trees (which is arguably difficult).
Or we could just introduce a restriction to ShouldHaveRules method. If we enforce users to have the exact same lambda expression in RuleFor and ShouldHaveRules then we could just compare string representations of the expressions and that would be it. By exact same I mean this (in terms of the example above):
public class ModelValidator : AbstractValidator<Model>
{
public ModelValidator()
{
RuleForEach(model => model.Numbers.Select(number => number + 1))
.GreaterThan(0);
}
}
public class ModelValidatorTests
{
[Fact]
public void ModelValidatorHasCorrectRules()
{
var validator = new ModelValidator();
validator.ShouldHaveRules(model => model.Numbers.Select(number => number + 1),
new ComparisonValidatorVerifier<GreaterThanValidator>(0));
}
}