Thursday, December 6, 2007

MS Unit Exception Testing

How do people handle exception expectations within the Microsoft unit testing framework built in to Visual Studio Team Edition? Of course there is the built in ExpectedExceptionAttribute where you mark the test itself with an exception which should be thrown while performing the unit test. If no exception is thrown it fails, if an exception of that type is thrown it passes, if the exception is of the wrong type it fails.

I don't know about others, but this rarely sits well with me. Typically when I write a test method I want the ability to test other conditions when an exception is thrown, or I want to use a single test method to test all exceptional cases within a single business object method. Do most people just write one test per case of a method? It seems like this would result in a lot of duplication of unit tests.

To address this issue I have historically used the following code:


[TestMethod]
public void TestSomeMethod()
{
try
{
SomeMethod();
Assert.Fail("Expected ExpectedException to be thrown "
+ "while calling SomeMethod.");
}
catch (ExpectedException) { }
}


Recently it occurred to me, why do I have to go through this process every time I want to test for exceptions. At first I was hopeful that an additional mechanism would be made available with Visual Studio 2008 MSUnit, but it was not. I then decided to investigating the ability to extend the framework.

Honestly, I feel a little dirty saying this, but this appears to be a good use case for Extension Methods. Yes, I know my previous statements regarding extension methods (C# 3.0 Extension Methods? A Good Idea?), but I honestly think this would be a good use of it.

Imagine writing the following code instead:

[TestMethod]
public void TestSomeMethod()
{
Assert.ThrowsException<ExpectedException>
(delegate { SomeMethod(); });
}


Wow, I really would enjoy that functionality if it was available to me. Unfortunately for me, I can not make this an extension methods. Extension methods are only allowed on instances of objects. Since Assert is a static class you can not add extension methods. So instead of using an extension method we are instead left with using our own custom static class for testing.

See the implementation of this below:

public static class CustomAssert
{
public delegate void MethodDelegate();

public static void ThrowsException<T>
(MethodDelegate method)
where T: Exception
{
ThrowsException<T>(method,
"Expected Exception of type "
+ typeof(T).ToString()
+ " was not thrown.");
}

public static void ThrowsException<T>
(MethodDelegate method,
string message)
where
T : Exception
{
try
{
method.DynamicInvoke();
Assert.Fail(message);
}
catch (T) { }
}
}


and now the final tests looks like:

[TestMethod]
public void TestMethod1()
{
CustomAssert.ThrowsException<ApplicationException>
(delegate { SomeMethod(); });
CustomAssert.ThrowsException<ApplicationException>
(delegate { SecondMethod(5); });
}
In all honesty when I first decided this was something I was interested in I thought extension methods would work for this. While writing the code for this blog I came to the realization that they wouldn't.

While researching how to make this approach work I did stumble across the xUnit.Net framework. It looks like the xUnit.Net framework already supports very similar functionality to what I am describing in this post. If you're interested with a unit testing framework with functionality like what I described maybe that is where you should look. If you want to stick to the existing Microsoft MS Unit framework, this may be a good approach to better handle your exception testing needs.

--John Chapman

1 comment:

Scott said...

John,

Thanks for the article, one quick update though (not sure if this is vs2008 specific or just a quirk of the system exceptions I was trying to catch)... you need to add an additional catch statement in the ThrowsException method to look something like this:

try
{
method.DynamicInvoke();
Assert.Fail(message);
}
catch (TargetInvocationException ex)
{
Exception inner = ex.InnerException;
if (typeof(T) != inner.GetType())
{
Assert.Fail(message);
}
}
catch (T)
{
}

Blogger Syntax Highliter