Prescott,
When creating a unit test, our goal is to test the contract that a class represents.
So, we should always start with "What is the contract for this class?".
Exceptions tend to be very light on contract. For the most part, an exception exposes relatively few properties and most custom exceptions simply inherit directly from System.Exception and don't add any functionality or custom properties.
Example:
public class MyException : Exception
{
public MyException(string message) : base(message)
{
}
}
There's really nothing to test here. The only thing you could possibly test is that yes, when I pass a string in the constructor for 'message' parameter, the Message property contains the same string I passed in.
But, according to Microsoft guidelines, exceptions should be serializable. Which means they need to Serializable attribute and the appropriate constructor. The example above doesn't have that.. So we don't need to test it, but assuming it was written correctly like this:
[Serializable]
public class MyException : Exception
{
//
// For guidelines regarding the creation of new exception types, see
// and
//
public MyException()
{
}
public MyException(string message)
: base(message)
{
}
public MyException(string message, Exception inner)
: base(message, inner)
{
}
protected MyException(
SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
Then we should unit test that it correctly serializes and deserializes... Since it doesn't add any behaviour or contract to the existing Exception type, testing this is kind of pointless. We can assume the BCL types function as expected. Regardless, if they don't we can't fix it.
But if it does add something... like for example this:
[Serializable]
public class ParseException : Exception
{
//
// For guidelines regarding the creation of new exception types, see
// and
//
public ParseException()
{
}
public ParseException(string message)
: base(message)
{
}
public ParseException(string message, Exception inner)
: base(message, inner)
{
}
public ParseException(string message, IToken token)
: base(message)
{
Token = token;
}
public ParseException(string message, IToken token, Exception inner)
: base(message, inner)
{
Token = token;
}
protected ParseException(
SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
public IToken Token { get; private set; }
}
We've added the Token property. Serialization may no longer work correctly if the Token instance is not serializable or custom serialization for that type didn't get implemented correctly.
In the case of a custom property on an Exception like this, we should then have a unit test for each constructor, validating that the properties were correctly assigned via the constructor overloads and that serialization works as expected for all members.
This may seem like overkill for something as simple as an exception... but one rule of thumb is that there's no such thing as too many unit tests covering too many cases. I'd hate to try to debug something that had crashed, thrown this exception and then got a serialization exception when it tried to serialize it and it failed. It would hide the real problem which threw the first exception and I'd be scratching my head trying to figure out what went wrong with serialization, when what I should be focusing on is what went wrong with parsing.
Your best bet is to avoid complex behaviour in an exception in the first place, as it's a bad place to introduce potentially faulty behaviour, since you only ever use them when something else has already failed. Can lead to the above described situation pretty quick and be a real brain drain for debugging.
Thanks,
Troy