Sunday 27 December 2015

Testing exceptions with NUnit 3.x

If you are moving from NUnit 2.x to 3.x you will find that the old ExpectedException attribute is missing. Not to worry. There are alternatives.

Here’s an example. In Domain-Driven Design (DDD) there's a concept of an 'entity'; an object with an identifier. I have been experienting with a supporting framework for DDD which includes a base class for entities. I decided I didn’t want entities to be instantiated without an identifier and I didn’t want that identifier to be the default value for the identity type. Here’s a simple entity class together with a fragment of the base class:

private class Customer : Entity<int>
{
    public Customer(int id) : base(id)
    { 
    }
}

public abstract class Entity<T> : IEquatable<Entity<T>>
{
    protected Entity(T id)
    {
        if (object.Equals(id, default(T)))
        {
            throw new ArgumentException("The ID cannot be the type's default value.", "id");
        }

        this.Id = id;
    }

    // ... snip ...
}

With 2.x tests you could write something like this:

[Test]
[ExpectedException(typeof(System.ArgumentException))]
public void EntityConstructor_WithDefaultValue_ThrowsException()
{
    var customer1 = new Customer(default(int));
}

If you have tests like that and you update NUnit to 3.x that won’t even compile because the attribute isn’t there anymore. No need to panic though, there are a couple of options available to you. The first is to use Exception Asserts (i.e. the Assert.Throws method).

[Test]
public void EntityConstructor_WithDefaultValue_ThrowsException()
{
    Assert.Throws<ArgumentException>(() => new Customer(default(int)));
}

The second option is to use the Throws Constraint which is my preference.

/// 
/// Tests that the ID can't be the types default value.
/// 
[Test]
public void EntityConstructor_WithDefaultValue_ThrowsException()
{
    Assert.That(() => new Customer(default(int)), Throws.ArgumentException);
}

Actually the last 2 alternatives have a subtle improvement over the ExpectedException attribute.

The attribute approach doesn’t allow you specify exactly when and where the exception is expected to be thrown. If you have a test with multiple lines of setup code any one of those lines could throw an exception which would be caught by the attribute (assuming the exception type is correct) so you might not be testing what you think you are testing.

The assertion approach allows you to specify the exact line of code you expect to throw the exception.