Search code examples
c#unit-testingdata-annotationsvalidationattribute

Testing custom validation of property depenent on other property returns ArgumentNullException


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?


Solution

  • 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>