Testing FluentValidation ChildRules

1

Given the following object:

public class PatchDTO
{
    public PatchDTO()
    {
        Data = new List<Datum>();
    }
    public List<Datum> Data { get; set; }

    public class Datum
    {
        public Datum()
        {
            Attributes = new Dictionary<string, object>();
        }
        public string Id { get; set; }
        public Dictionary<string, object> Attributes { get; set; }
    }
}

I have my validator set as follows:

RuleFor(oo => oo.Data)
    .NotEmpty()
    .WithMessage("One or more Data blocks must be provided");

RuleForEach(d => d.Data).ChildRules(datum =>
{
    datum.RuleFor(d => d.Id)
        .NotEmpty()
        .WithMessage("Invalid 'Data.Id' value");
});

Which I'm trying to test using the test extensions as such:

[Theory]
[InlineData(null)]
[InlineData("")]
public void Id_Failure(string id)
{
    dto.Data[0].Id = id;
    var result = validator.TestValidate(dto);
    result.ShouldHaveValidationErrorFor(oo => oo.Data[0].Id)
        .WithErrorMessage("Invalid 'Data.Id' value");
}

But when I run the test it says:

FluentValidation.TestHelper.ValidationTestException
  HResult=0x80131500
  Message=Expected a validation error for property Id
----
Properties with Validation Errors:
[0]: Data[0].Id

But as you can see under the 'Validation Errors', it has actually picked up in the validation failure but isn't tying it to this test. So how do I test these ChildRules or tell the test extension method which property it should actually be checking?

(I also used the validator.ShouldHaveValidationErrorFor directly with the same results)

c#
.net-core
fluentvalidation
asked on Stack Overflow Feb 28, 2020 by Jim N • edited Feb 28, 2020 by Greg Burghardt

1 Answer

1

I've had this problem before and resorted to using the string overload for ShouldHaveValidationErrorFor

The following (nunit) test passes

[TestCase(null)]
[TestCase("")]
public void Id_InvalidValue_HasError(string id)
{
    var fixture = new Fixture();
    var datum = fixture.Build<PatchDTO.Datum>().With(x => x.Id, id).Create();
    var dto = fixture.Build<PatchDTO>().With(x => x.Data, new List<PatchDTO.Datum> { datum }).Create();

    var validator = new PatchDTOValidator();

    var validationResult = validator.TestValidate(dto);

    validationResult.ShouldHaveValidationErrorFor("Data[0].Id")
        .WithErrorMessage("Invalid 'Data.Id' value");
}

It's been a while since I looked at it, but I believe the issue is in the extension ShouldHaveValidationErrorFor matching on property name and the property expression overload doesn't resolve the property name to 'Data[0].Id'. If you inspect the validation results you'll get a ValidationError object that looks something like this

{
   "PropertyName":"Data[0].Id",
   "ErrorMessage":"Invalid 'Data.Id' value",
   "AttemptedValue":"",
   "CustomState":null,
   "Severity":0,
   "ErrorCode":"NotEmptyValidator",
   "FormattedMessageArguments":[

   ],
   "FormattedMessagePlaceholderValues":{
      "PropertyName":"Id",
      "PropertyValue":""
   },
   "ResourceName":null
}

EDIT:

Had a quick peek into the property expression overload, as per below

public IEnumerable<ValidationFailure> ShouldHaveValidationErrorFor<TProperty>(Expression<Func<T, TProperty>> memberAccessor)
{
  return ValidationTestExtension.ShouldHaveValidationError(this.Errors, ValidatorOptions.PropertyNameResolver(typeof (T), memberAccessor.GetMember<T, TProperty>(), (LambdaExpression) memberAccessor), true);
}

Presumably you could use another/write your own property name resolver to handle the case as it is settable. You'd probably have to dig into the expression to do it.

answered on Stack Overflow Feb 29, 2020 by rgvlee • edited Feb 29, 2020 by rgvlee

User contributions licensed under CC BY-SA 3.0