Syntactic Sugar for Thrown Exception Checking with Generics

Using attributes like [ExpectedException] can obscure intent because they apply to the whole method instead of a specific call you're checking. To make intent totally clear and prevent unexpected bugs, you should wrap just the statement you're expecting an exception from in a try/catch:

int y = 0;

try
{
int x = 1 / y;
Assert.Fail("Exception not thrown DivideByZeroException");
}
catch (DivideByZeroException)
{
}

In addition, at times the same exception is thrown, but with different descriptive messages and in the interest of checking correctness we need to check the message as well.  To help achieve DRY, clarity, and expressiveness, I've created some helper methods for my unit tests to deal with testing proper exception throwing.  Note that to be totally by the book, you likely should be creating special custom exceptions for each type rather than checking the message strings, but this can be superfluous at times so the option is here.

public class SpecHelpers
{
public static void ExpectException<T>(Action methodCall) where T : Exception
{
bool caught = false;

try
{
methodCall();
}
catch (T)
{
caught = true;
}

if (!caught)
Assert.Fail(String.Format("Expected exception {0} not thrown"));
}

public static void ExpectExceptionMessageStartsWith<T>(Action methodCall, string startsWith) where T : Exception
{
bool caught = false;

try
{
methodCall();
}
catch (T ex)
{
if (!ex.Message.StartsWith(startsWith, true, null))
Assert.Fail(String.Format("Expected exception {0} thrown, but message didn't start with \"{1}\". Message: \"{2}\"",
ex.GetType().FullName, startsWith, ex.Message));

caught = true;
}

if (!caught)
Assert.Fail(String.Format("Expected exception {0} not thrown"), typeof(T).FullName);
}

public static void ExpectExceptionMessageContains<T>(Action methodCall, string contains) where T : Exception
{
bool caught = false;

try
{
methodCall();
}
catch (T ex)
{
if (ex.Message.IndexOf(contains, StringComparison.InvariantCultureIgnoreCase) < 0)
Assert.Fail(String.Format("Expected exception {0} thrown, but message didn't contain \"{1}\". Message: \"{2}\"",
ex.GetType().FullName, contains, ex.Message));

caught = true;
}

if (!caught)
Assert.Fail(String.Format("Expected exception {0} not thrown"), typeof(T).FullName);
}
}

 

 some example calls:

private bool ThrowEx()
{
throw new Exception();
}

private bool ThrowExContains()
{
throw new NullReferenceException("a special message");
}

[Test]
public void TestExceptionWrapper()
{
// .NET 3.5 style with lamba notation
SpecHelpers.ExpectException<Exception>(() => ThrowEx());
SpecHelpers.ExpectExceptionMessageContains<NullReferenceException>(() => ThrowExContains(), "special");

// .NET 2.0 style with anonymous delegate
SpecHelpers.ExpectExceptionMessageContains<NullReferenceException>(delegate() { ThrowExContains(); }, "special");
}

Share this post: Email it! | bookmark it! | digg it! | reddit!


Published Saturday, October 25, 2008 8:52 AM by alecl
Filed under ,

Comments

 

mjcarrabine said:

I am new to unit testing (using MSTest). Having quickly discovered the limitations of the ExpectedException attribute, I was searching for better approach, and this is tremendously helpful.

One question, how would I modify your helper functions to allow methodcalls that take parameters?

I would expect it would use Func<T,TResult> instead of Action, but I am also new to delegates, so I am unsure if this is possible.

Thanks again.

March 16, 2009 3:40 PM