Search code examples
c#unit-testingxunitfluentvalidation

How to unit test an exception throwing for each validation error using XUnit


I have the following method which uses FluentValidation to validate a ConnectionModel:

    internal static bool Validate(ConnectionModel connectionModel)
    {
        var validator = new ConnectionModelValidator();

        var result = validator.Validate(connectionModel);

        if (result.IsValid) return true;

        foreach (var failure in result.Errors)
            throw new ArgumentNullException(failure.ErrorMessage);

        return false;
    }

How can I unit test, using XUnit, the following bits which should throw ArgumentNullException for each validation error:

        foreach (var failure in result.Errors)
            throw new ArgumentNullException(failure.ErrorMessage);

        return false;

Here is what I tried so far:

    public void Validate_ShouldThrowArgumentNullExceptionIfConnectionModelHasEmptyProperty()
    {
        var connectionModel = new ConnectionModel
        {
            JumpHostname = "10.1.1.1",
            JumpUsername = "test",
            SaaHostname = "test",
            SaaPassword = "test",
            SaaUsername = "test",
            SidePassword = "test",
            SideServiceName = "test",
        };
        var validator = new ConnectionModelValidator();

        var result = validator.Validate(connectionModel);

        Assert.NotEmpty(result.Errors);

    }

but this scenario only covers Errors not being empty.


Solution

  • First, you shouldn't throw an exception for business logic validations. For example if you are validating user input, failed validation is not exceptional. Exceptions wasn't designed for handling a workflow.
    Instead return validation result object and leave consumer of the validator to decide what how to return/display failed results to the user.

    Second, throwing exceptions in the loop make no sense, because first thrown exception will return to the top of the stack and will "ignore" rest of exceptions.

    Instead (if still want to throw exceptions) throw one and "specific" exception with all errors included in it.

    public class InvalidConnectionModelException : Exception
    {
        public string[] ErrorMessages { get; }
    
        public InvalidConnectionModelException(string[] errorMessages)
        {
            ErrorMessages = errorMessages;
        }
    }
    
    // Throw own exception
    internal static bool Validate(ConnectionModel connectionModel)
    {
        var validator = new ConnectionModelValidator();
        var result = validator.Validate(connectionModel);
    
        if (result.IsValid) 
        {
            return true;
        }
    
        var errorMessages = result.Errors.Select(error => error.ErrorMessage).ToArray();
        throw new InvalidConnectionModelException(errorMessages);
    }
    

    With custom exception validation can be tested simpler

    var invalidModel = new ConnectionModel { ... };
    
    var validator = new ConnectionModelValidator();
    Action validate = () => validator.Validate(invalidModel);
    
    Assert.Throws<InvalidConnectionModelException>(validate);
    

    But again, I would suggest reconsider throwing exception and would return validation result to the consumer.