Search code examples
c#fluentvalidation

Identifying a specific failed validator for a property with multiple validators


In Fluent Validation, I have multiple validators on a single property, like so:

RuleFor(c => c.MinSize)
    .GreaterThanOrEqualTo(limits.MinLimit)
    .LessThan(c => c.PreferredSize ?? limits.PreferredLimit);

I write two separate tests, each one verifying the behavior of one of those validators:

[Test]
public void Fail_when_min_below_min_limit()
{
    var resource = new QualityDefinitionResource
    {
        MinSize = -1
    };

    var limits = new QualityDefinitionLimits();
    var sut = new QualityDefinitionResourceValidator(limits);

    var result = sut.TestValidate(resource);

    result.ShouldHaveValidationErrorFor(c => c.MinSize);
}

[Test]
public void Fail_when_min_above_preferred_size()
{
    var resource = new QualityDefinitionResource
    {
        MinSize = 10,
        PreferredSize = 8,
        MaxSize = 100
    };

    var limits = new QualityDefinitionLimits();
    var sut = new QualityDefinitionResourceValidator(limits);

    var result = sut.TestValidate(resource);

    result.ShouldHaveValidationErrorFor(c => c.MinSize);
}

I'd like the ability to verify which specific validator failed in each of these test methods. The reason I find this important is because the incorrect validator could be kicking in and the test won't validate that.

I've thought of relying on the message (i.e. chaining .WithMessage() on ShouldHaveValidationErrorFor()), but this makes the test a bit too fickle, because those messages may change. Wildcard support might work, but I wouldn't be able to rely on .WithMessage() for that.

Without testing the message, what other options do I have for validating the specific validation that failed?


Solution

  • You could validate WithErrorCode. Either check WithErrorCode("GreaterThanOrEqualValidator"), or if you don't like magic strings, you could define your own codes.

    E.g.:

    public record Resource(int MinSize);
    
    public class Validator : AbstractValidator<Resource> 
    {
      public enum Err { E001, E002, E003 }
    
      public Validator() {
        RuleFor(c => c.MinSize)
          .GreaterThanOrEqualTo(0) // Default err code
          .LessThan(8).WithErrorCode(nameof(Err.E002)) // Custom err code
          ;
      }
    }
    

    And validate:

    void Test() {
      Validator sut = new();
      
      Resource resource = new(-1);
      sut.TestValidate(resource)
        .ShouldHaveValidationErrorFor(c => c.MinSize)
        .WithErrorCode("GreaterThanOrEqualValidator"); // Magic string
    
      resource = new(10);
      sut.TestValidate(resource)
        .ShouldHaveValidationErrorFor(c => c.MinSize)
        .WithErrorCode(nameof(Validator.Err.E002)); // less magical
    }