The traditional method to test for exceptions with the Microsoft unit testing framework is to use the ExpectedException attribute.
This method has shortcomings. The biggest is that it checks the whole method for the exception which can lead to false positives. Here is a contrived example to show what I am talking about.
The StringlyType class is by the way, not serious.
// class under test public class StringlyType { private StringBuilder stringlyTypeBuilder = null; // contrived setup method public void Setup() { stringlyTypeBuilder = new StringBuilder(); } public string Stringlyfy(object toStringlyFy) { stringlyTypeBuilder.Append(toStringlyFy.ToString()); return stringlyTypeBuilder.ToString(); } } [TestClass] public class UnitTest1 { [TestMethod] [ExpectedException(typeof(NullReferenceException))] public void TraditionalExpectedException() { // Arrange var classUnderTest = new StringlyType(); int toStringlyfy = 1; // Act classUnderTest.Stringlyfy(toStringlyfy); } [TestMethod] [ExpectedException(typeof(NullReferenceException))] public void TraditionalExpectedExceptionFalsePositive() { // Arrange var classUnderTest = new StringlyType(); StringlyType randomClass = null; randomClass.Setup(); int toStringlyfy = 1; // Act classUnderTest.Stringlyfy(toStringlyfy); }
The first test method will pass. We haven’t called the Setup method on StringlyType so the call to Append will throw our NullReferenceException.
On the second test method, we will get a false positive. The call to randomClass.Setup() will throw an exception and our test will be none the wiser. If we change the behaviour of StringlyType so that the StringBuilder is initialized in the constructor, the false positive will keep on telling us the code is throwing an exception.
A better way
NUnit, an open-source alternative to Microsoft’s framework, has been using a better approach for years. Their approach is having an Assert.Throws that works like an ordinary assert.
When I am in an environment that isn’t using NUnit I like to create something equivalent along the lines of:
public static class AssertExtension { public static T Throws<T>(Action expressionUnderTest, string exceptionMessage = "Expected exception has not been thrown by target of invocation." ) where T : Exception { try { expressionUnderTest(); } catch (T exception) { return exception; } Assert.Fail(exceptionMessage); return null; } }
Since the Assert class is static we can’t create extension methods for it, hence I’m creating another AssertExtension class.
This AssertExtension class will allow to write the previous tests in the following way.
[TestMethod] public void ExampleWithAssertThrows() { // Arrange var underTest = new StringlyType(); int t = 1; // Act && Assert AssertExtension.Throws<NullReferenceException>(() => underTest.Stringlyfy(t)); } [TestMethod] public void WillNotGetAFalsePositive() { // Arrange var underTest = new StringlyType(); StringlyType randomClass = null; randomClass.Setup(); int t= 1; // Act && Assert AssertExtension.Throws<NullReferenceException>(() => underTest.Stringlyfy(t)); }
Notice that the second method we will not be a false positive.
This method has several advantages:
- prevents false positives
- easier to see which statement should throw an exception
- more succinct and idiomatic than using a try catch
- allows to return the thrown exception
Here is an example of the last point:
var thrownException = AssertExtension.Throws<InvalidOperationException>( () => classUnderTest.Stringlyfy(toStringlyfy)); Assert.AreEqual("some message", thrownException.Message);
I made a GitHub Gist if you want to get/modify the AssertExtension class.
I used the same approach recently. I also created the DoesNotThrow to check nothing is thrown. Both help the reader to understand tests. In the case of DoesNotThrow, I used to have no Assert in the test, and got grief from it. Hence making an obvious statement out of it.
Here is mine:
public static class AssertHelper
{
static public void DoesNoThrow(Action a, string errorMessage) where T:Exception
{
string txt = “”;
var threw = false;
try
{
a();
}
catch (T e)
{
threw = true;
txt = e.Message;
}
Assert.IsFalse(threw,string.Format(“Error:{0}\nException Message:\n{1}”,errorMessage,txt));
}
static public void Throws(Action a, string errorMessage) where T:Exception
{
var threw = false;
try
{
a();
}
catch (T content)
{
threw = true;
}
Assert.IsTrue(threw, errorMessage);
}
}