In a lot of TDD tutorials I see code like this:
public class MyClass
{
public void DoSomething(string Data)
{
if (String.IsNullOrWhiteSpace(Data))
throw new NullReferenceException();
}
}
[Test]
public DoSomething_NullParameter_ThrowsException()
{
var logic = MyClass();
Assert.Throws<NullReferenceException>(() => logic.DoSomething(null));
}
This all makes sense but at some point you are going to get to the class that actually uses MyClass and you want to test that the exception is handled:
public class EntryPointClass
{
public void DoIt(string Data)
{
var logicClass = new MyClass();
try
{
logicClass.DoSomething(Data);
}
catch(NullReferenceException ex)
{
}
}
}
[Test]
public DoIt_NullParameter_IsHandled()
{
var logic = new EntryPointClass()
try
{
logic.DoIt(null);
}
catch
{
Assert.Fail();
}
}
So why not put the try/catch in MyClass in the first place rather than throwing the exception and have the test that test’s for null in the MyClass unit test class and not in the EntryPointClass unit test class?
Typically, your exception handling will look like this:
(The
Loggeris just an example for a resource you need for proper exception handling not available at the place whereMyClasslives. You could also add the display of a message box here or something like that.)At the level of
MyClass, you typically don’t have the appropriate tools available for proper exception handling (and you don’t want to add them there to keep the class decoupled from, for example, a specific logging mechanism).Note that this design decision is independent from doing TDD. If you want your class
MyClassactually catching the exceptions by itself, you have to write your tests differently, that is right. But that is only a good idea if catching the exceptions does not block your automatic tests. Try, for example, to write a good unit test forMyClasswhenMyClassshows a hardcoded warning dialog by itself.Of course, the example above shows that unit testing
EntryPointClassmay get harder in reality when you first need something like a logger to get things working. In general, you can attack this problem by providing the logger at constrcution time utilizing an ILogger interface which allows the logger to be replaced by mock. Or in this simple case, it may be enough not to initialize the logger for your unit tests at all and code it like this: