2

I'm trying to write an XUnit test to test my custom validator. The validator checks the value of other property which indicates if the validated property should be null or have a value. However the test returns ArgumentNullException when I'm using TryValidateProperty method.

Validator:

public class ConcatenatedDataValidator : ValidationAttribute
{
    public string PropertyName { get; private set; }
    public ConcatenatedDataValidator(string propertyName)
    {
        this.PropertyName = propertyName;
    }


    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var property = validationContext.ObjectType.GetProperty(PropertyName);
        if(property == null && value != null)
        {
            return new ValidationResult(string.Format("{0} is null", PropertyName));
        }
        var chosenValue = property.GetValue(validationContext.ObjectInstance, null);

        if(chosenValue.Equals("00") && (value == null || value.Equals(string.Empty))
            ||  chosenValue.Equals("01") && value != null) 
        {
            return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
        }

        return null;
    }  
}

Test:

public class ConcatenatedDataValidatorTests
{
    private TestedModel model;
    private ValidationContext context;
    private List<ValidationResult> results;

    [Fact]
    public void IsValid_OtherValueIs00ThisValueIsNull_ReturnFalse()
    {
        // arrange
        var concatenatedDataValidator = new ConcatenatedDataValidator("OtherProperty");
        model = new TestedModel();
        model.OtherProperty = "00";
        model.ThisProperty = null;
        context = new ValidationContext(model);
        results = new List<ValidationResult>();

        // act
        var result = Validator.TryValidateProperty(model.ThisProperty, context, results);

        // assert
        Assert.False(result);

    }
}

The test returns

System.ArgumentNullException : Value cannot be null. Parameter name: propertyName

in the act part. I'd like to test just this one property, as in the model I have plenty of other properties with Required attribute and I wish to keep the tests as simple as possible and test only my custom validator. Is there any way to solve this problem?

Lordzio
  • 47
  • 5

1 Answers1

3

This is expected behaviour. As the Validator.TryValidateProperty specifies that if value is null the System.ArgumentNullException is thrown.

If you want to test this behaviour / the case of your propertie's value being null then you should catch the exception and check for it - although thats basically testing the .NET framework.

This also indicates that you can cut out the value == null check in your IsValid method as it will never trigger.

UPDATE

The whole error refers to the private method EnsureValidPropertyType called inside TryValidateProperty (see here).

This is caused because of ValidationContext.MemberName.

To solve the issue, you have to set the MemberName during the creation of your ValidationContext (if .NET 4.7.2 and earlier)

ValidationContext context = new ValidationContext(model)
{
        MemberName = "ThisProperty"
};

or change the default behaviour in your app config (.NET 4.8).

<configuration>
   <appSettings>
      <add key="aspnet:GetValidationMemberName" value="true" />
   </appSettings>
</configuration>
FeRaaC
  • 486
  • 2
  • 10
  • What about a test case when I expect the value to not be null. When I type the `model.ThisProperty = "asd";` in my test it still produces the same exception. And it still talks about _propertyName_ argument passed in constructor. – Lordzio Aug 09 '19 at 10:19
  • Also `ValidationAttribute.IsValid` method doesn't return `System.ArgumentNullException` and doesn't indicate that the value passed can't be null. There can be a situation where I just don't pass anything to that property even though I've indicated in the other one that I want to pass it here. I'm basically trying to do a validator that makes a property required depending on another property's value. – Lordzio Aug 09 '19 at 10:28
  • Oh, yea it refers to the private method `EnsureValidPropertyType` called inside `TryValidateProperty` (see [here](https://github.com/microsoft/referencesource/blob/e0bf122d0e52a42688b92bb4be2cfd66ca3c2f07/System.ComponentModel.DataAnnotations/DataAnnotations/Validator.cs#L40)). This is caused because of [`ValidationContext.MemberName`](https://learn.microsoft.com/de-de/dotnet/api/system.componentmodel.dataannotations.validationcontext.membername?view=netframework-4.8). I update my answer with the solution to this. – FeRaaC Aug 09 '19 at 11:34
  • 1
    Yes, the solution from update works perfectly. Thank you so very much :) EDIT: Tested on .NET 4.6.1 – Lordzio Aug 09 '19 at 11:46
  • @Lordzio that's great. Just to make sure `model.ThisProperty = null` still should throw the exception though. – FeRaaC Aug 09 '19 at 11:56
  • MemberName = "ThisProperty" saved me thank you – user3625699 Apr 21 '22 at 00:47