Fluent Validation chain rule not working with multiple When conditions

Viewed 6253

I got a really interesting behavior. I have the two test cases below:

[Fact]
public void Ctor_WhenNeverIsTrueAndAfterOcurrenceIsNotNull_HasError()
{
    // arrange
    var reccurenceEnd = new RecurrenceEnd()
    {
         IsNever = true,
         AfterOccurence = 1
     };

     // act
     var validator = GetValidator();

     // assert
     validator.ShouldHaveValidationErrorFor(p => p.AfterOccurence, reccurenceEnd);
}

[Fact]
public void Ctor_WhenNeverIsFalseAndAfterOccurenceIsNullAndByDateIsNull_HasError()
{
    // arrange
    var reccurenceEnd = new RecurrenceEnd()
    {
        IsNever = false,
        AfterOccurence = null,
        ByDate = null
    };

    // act
    var validator = GetValidator();

    // assert
    validator.ShouldHaveValidationErrorFor(p => p.AfterOccurence, reccurenceEnd);
}

On my validator, if I have the following, the first test fails and the second passes. If I change the order of the rules, the first test passes and the second fails.

RuleFor(dto => dto.AfterOccurence)
    .Cascade(CascadeMode.StopOnFirstFailure)
    .Null()
        .When(p => p.IsNever == true)
    .NotEmpty()
        .When(p => p.IsNever == false && p.ByDate == null);

If I change my validator to the following, both tests pass.

RuleFor(dto => dto.AfterOccurence)
    .Null()
        .When(p => p.IsNever);
RuleFor(dto => dto.AfterOccurence)
    .NotEmpty()
        .When(p => p.IsNever == false && p.ByDate == null);

Am I setting up wrong?

1 Answers

Ok, got an answer from FluentValidation's github-issues:

Hi, this is related to you use of the When condition. By default, a When condition applies to all previous validators in the same RuleFor call, so when you use a single call to RuleFor, you’re essentially doubling up on conditions. You should either use separate calls to RuleFor (as you have in your second example), or use the other overload of When that allows you to specify ApplyConditionTo.CurrentValidator

The second solution would be:

RuleFor(dto => dto.AfterOccurence)
    .Cascade(CascadeMode.StopOnFirstFailure)
    .Null()
        .When(p => p.IsNever == true)
    .NotEmpty()
        .When(p => p.IsNever == false && p.ByDate == null, ApplyConditionTo.CurrentValidator);
Related