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:
public class EntryPointClass
{
Logger _logfile;
// ...
public void DoIt(string Data)
{
var logicClass = new MyClass();
try
{
logicClass.DoSomething(Data);
}
catch(NullReferenceException ex)
{
_logfile.WriteLine("null reference exception occured in method 'DoIt'");
}
}
}
(The Logger
is just an example for a resource you need for proper exception handling not available at the place where MyClass
lives. 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 MyClass
actually 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 for MyClass
when MyClass
shows a hardcoded warning dialog by itself.
Of course, the example above shows that unit testing EntryPointClass
may 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:
public class EntryPointClass
{
// ....
catch(NullReferenceException ex)
{
if(_logfile!=null)
_logfile.WriteLine("null reference exception occured in method 'DoIt'");
}
// ....
}
Why not put the try/catch in MyClass in the first place...
Exceptions are for decoupling the error detection and the error handling. If the error cannot be handled (in all cases) locally in MyClass, then You should throw an exception.
This allows a local error detection (MyClass) and an error handling by whoever catches the exception.
The test in EntryPointClass
tests, if the method DoIt is null-safe. It has little to do with its use of MyClass.
In other words: The test in EntryPointClass
does not and (for code stability and encapsulation issues) should not rely on the fact that MyClass
is used.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With