104
votes

Do you think that this is a good way for testing exceptions? Any suggestions?

Exception exception = null;
try{
    //I m sure that an exeption will happen here
}
catch (Exception ex){
    exception = ex;
}

Assert.IsNotNull(exception);

I'm using MS Test.

10

10 Answers

143
votes

I have a couple of different patterns that I use. I use the ExpectedException attribute most of the time when an exception is expected. This suffices for most cases, however, there are some cases when this is not sufficient. The exception may not be catchable - since it's thrown by a method that is invoked by reflection - or perhaps I just want to check that other conditions hold, say a transaction is rolled back or some value has still been set. In these cases I wrap it in a try/catch block that expects the exact exception, does an Assert.Fail if the code succeeds and also catches generic exceptions to make sure that a different exception is not thrown.

First case:

[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void MethodTest()
{
     var obj = new ClassRequiringNonNullParameter( null );
}

Second case:

[TestMethod]
public void MethodTest()
{
    try
    {
        var obj = new ClassRequiringNonNullParameter( null );
        Assert.Fail("An exception should have been thrown");
    }
    catch (ArgumentNullException ae)
    {
        Assert.AreEqual( "Parameter cannot be null or empty.", ae.Message );
    }
    catch (Exception e)
    {
        Assert.Fail(
             string.Format( "Unexpected exception of type {0} caught: {1}",
                            e.GetType(), e.Message )
        );
    }
}
54
votes

Now, 2017, you can do it easier with the new MSTest V2 Framework:

Assert.ThrowsException<Exception>(() => myClass.MyMethodWithError());

//async version
await Assert.ThrowsExceptionAsync<SomeException>(
  () => myObject.SomeMethodAsync()
);
19
votes

Edit : MS Test Framework belatedly has copied other Unit test frameworks and does now have Assert.ThrowsException and Assert.ThrowsExceptionAsync which behave similar to the NUnit equivalents.

However, at time of writing, there is still no direct equivalent of Assert.Catch<TException> which allows testing for TException or a subclass of TException, so your unit tests will need to be exact about the setup and exeptions which are tested. From MS Test Docs:

Tests whether the code specified by delegate action throws exact given exception of type T (and not of derived type) and throws AssertFailedException if code does not throws exception or throws exception of type other than T.

Prior to SDK 2017

MS needs to catch up to features available in other testing frameworks. e.g. As of v 2.5, NUnit has the following method-level Asserts for testing exceptions:

Assert.Throws, which will test for an exact exception type:

Assert.Throws<NullReferenceException>(() => someNullObject.ToString());

And Assert.Catch, which will test for an exception of a given type, or an exception type derived from this type:

Assert.Catch<Exception>(() => someNullObject.ToString());

As an aside, when debugging unit tests which throw exceptions, you may want to prevent VS from breaking on the exception.

Edit

Just to give an example of Matthew's comment below, the return of the generic Assert.Throws and Assert.Catch is the exception with the type of the exception, which you can then examine for further inspection:

// The type of ex is that of the generic type parameter (SqlException)
var ex = Assert.Throws<SqlException>(() => MethodWhichDeadlocks());
Assert.AreEqual(1205, ex.Number);
16
votes

I'm new here and don't have the reputation to comment or downvote, but wanted to point out a flaw in the example in Andy White's reply:

try
{
    SomethingThatCausesAnException();
    Assert.Fail("Should have exceptioned above!");
}
catch (Exception ex)
{
    // whatever logging code
}

In all unit testing frameworks I am familiar with, Assert.Fail works by throwing an exception, so the generic catch will actually mask the failure of the test. If SomethingThatCausesAnException() does not throw, the Assert.Fail will, but that will never bubble out to the test runner to indicate failure.

If you need to catch the expected exception (i.e., to assert certain details, like the message / properties on the exception), it's important to catch the specific expected type, and not the base Exception class. That would allow the Assert.Fail exception to bubble out (assuming you aren't throwing the same type of exception that your unit testing framework does), but still allow validation on the exception that was thrown by your SomethingThatCausesAnException() method.

13
votes

Unfortunately MSTest STILL only really has the ExpectedException attribute (just shows how much MS cares about MSTest) which IMO is pretty awful because it breaks the Arrange/Act/Assert pattern and it doesnt allow you to specify exactly which line of code you expect the exception to occur on.

When I'm using (/forced by a client) to use MSTest I always use this helper class:

public static class AssertException
{
    public static void Throws<TException>(Action action) where TException : Exception
    {
        try
        {
            action();
        }
        catch (Exception ex)
        {
            Assert.IsTrue(ex.GetType() == typeof(TException), "Expected exception of type " + typeof(TException) + " but type of " + ex.GetType() + " was thrown instead.");
            return;
        }
        Assert.Fail("Expected exception of type " + typeof(TException) + " but no exception was thrown.");
    }

    public static void Throws<TException>(Action action, string expectedMessage) where TException : Exception
    {
        try
        {
            action();
        }
        catch (Exception ex)
        {
            Assert.IsTrue(ex.GetType() == typeof(TException), "Expected exception of type " + typeof(TException) + " but type of " + ex.GetType() + " was thrown instead.");
            Assert.AreEqual(expectedMessage, ex.Message, "Expected exception with a message of '" + expectedMessage + "' but exception with message of '" + ex.Message + "' was thrown instead.");
            return;
        }
        Assert.Fail("Expected exception of type " + typeof(TException) + " but no exception was thrown.");
    }
}

Example of usage:

AssertException.Throws<ArgumentNullException>(() => classUnderTest.GetCustomer(null));
10
votes

As an alternative to using ExpectedException attribute, I sometimes define two helpful methods for my test classes:

AssertThrowsException() takes a delegate and asserts that it throws the expected exception with the expected message.

AssertDoesNotThrowException() takes the same delegate and asserts that it does not throw an exception.

This pairing can be very useful when you want to test that an exception is thrown in one case, but not the other.

Using them my unit test code might look like this:

ExceptionThrower callStartOp = delegate(){ testObj.StartOperation(); };

// Check exception is thrown correctly...
AssertThrowsException(callStartOp, typeof(InvalidOperationException), "StartOperation() called when not ready.");

testObj.Ready = true;

// Check exception is now not thrown...
AssertDoesNotThrowException(callStartOp);

Nice and neat huh?

My AssertThrowsException() and AssertDoesNotThrowException() methods are defined on a common base class as follows:

protected delegate void ExceptionThrower();

/// <summary>
/// Asserts that calling a method results in an exception of the stated type with the stated message.
/// </summary>
/// <param name="exceptionThrowingFunc">Delegate that calls the method to be tested.</param>
/// <param name="expectedExceptionType">The expected type of the exception, e.g. typeof(FormatException).</param>
/// <param name="expectedExceptionMessage">The expected exception message (or fragment of the whole message)</param>
protected void AssertThrowsException(ExceptionThrower exceptionThrowingFunc, Type expectedExceptionType, string expectedExceptionMessage)
{
    try
    {
        exceptionThrowingFunc();
        Assert.Fail("Call did not raise any exception, but one was expected.");
    }
    catch (NUnit.Framework.AssertionException)
    {
        // Ignore and rethrow NUnit exception
        throw;
    }
    catch (Exception ex)
    {
        Assert.IsInstanceOfType(expectedExceptionType, ex, "Exception raised was not the expected type.");
        Assert.IsTrue(ex.Message.Contains(expectedExceptionMessage), "Exception raised did not contain expected message. Expected=\"" + expectedExceptionMessage + "\", got \"" + ex.Message + "\"");
    }
}

/// <summary>
/// Asserts that calling a method does not throw an exception.
/// </summary>
/// <remarks>
/// This is typically only used in conjunction with <see cref="AssertThrowsException"/>. (e.g. once you have tested that an ExceptionThrower
/// method throws an exception then your test may fix the cause of the exception and then call this to make sure it is now fixed).
/// </remarks>
/// <param name="exceptionThrowingFunc">Delegate that calls the method to be tested.</param>
protected void AssertDoesNotThrowException(ExceptionThrower exceptionThrowingFunc)
{
    try
    {
        exceptionThrowingFunc();
    }
    catch (NUnit.Framework.AssertionException)
    {
        // Ignore and rethrow any NUnit exception
        throw;
    }
    catch (Exception ex)
    {
        Assert.Fail("Call raised an unexpected exception: " + ex.Message);
    }
}
4
votes

With most .net unit testing frameworks you can put an [ExpectedException] attribute on the test method. However this can't tell you that the exception happened at the point you expected it to. That's where xunit.net can help.

With xunit you have Assert.Throws, so you can do things like this:

    [Fact]
    public void CantDecrementBasketLineQuantityBelowZero()
    {
        var o = new Basket();
        var p = new Product {Id = 1, NetPrice = 23.45m};
        o.AddProduct(p, 1);
        Assert.Throws<BusinessException>(() => o.SetProductQuantity(p, -3));
    }

[Fact] is the xunit equivalent of [TestMethod]

4
votes

Mark the test with the ExpectedExceptionAttribute (this is the term in NUnit or MSTest; users of other unit testing frameworks may need to translate).

0
votes

Suggest using NUnit's clean delegate syntax.

Example for testing ArgumentNullExeption:

[Test]
[TestCase(null)]
public void FooCalculation_InvalidInput_ShouldThrowArgumentNullExeption(string text)
{
    var foo = new Foo();
    Assert.That(() => foo.Calculate(text), Throws.ArgumentNullExeption);

    //Or:
    Assert.That(() => foo.Calculate(text), Throws.Exception.TypeOf<ArgumentNullExeption>);
}
0
votes

Here is what I did to test an HttpRequestException thrown when the EnsureSuccessStatusCode extentions method is called (.NET Core 3.1 MS Test):

var result = await Assert.ThrowsExceptionAsync<HttpRequestException>(async ()=>
{
    await myService.SomeMethodAsync("test value");
}

Assert.AreEqual("Response status code does not indicate success: 401 (Unauthorized).", result);

The above tests whether the method SomeMethodAsync throws the exception of type T in this case HttpRequestException and then I can do more asseritons e.g. test that its not null, is of type HttpRequestException and the exception message matches the 401 Unauthorised string in the above example (HttpRequestException is based on the Exception class so you have access to all of its properites and methods).