简体   繁体   中英

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.

You could test that with:

Assert.Throws<ArgumentNullException>(() => validator.Validate(connectionModel));

Consider Stop Using Assert.Throws in Your BDD Unit Tests , though, which suggests you should instead use something like:

var exception = Assert.Catch(() => validator.Validate(connectionModel));
Assert.NotNull(exception);
Assert.InstanceOf<ArgumentNullException>(exception);

The theory being that the latter better fits the Arrange-Act-Assert layout and enables you to test multiple aspects of the expected exception.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM