Jim N
Jim N

Reputation: 23

Testing FluentValidation ChildRules

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)

Upvotes: 2

Views: 4887

Answers (1)

rgvlee
rgvlee

Reputation: 3193

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.

Upvotes: 3

Related Questions