Search code examples
c#unit-testingnunitmockingrhino-mocks

Unit Testing Multiple Levels Of Exceptions -- Where To Stop?


Let's pretend I have 3 classes that each have exactly one responsibly (and method). Let's also pretend that these classes have interfaces to facilitate dependency injection. Class A calls the interface for class B and class B calls the interface for class C. If class C throws a NotAPrimeNumberException (if say, the int parameter isn't a prime number), I would expect to have a unit test that makes sure that C throws an NotAPrimeNumberException if the passed in parameter isn't a prime number. So far so good.

I'm currently under the belief that the unit tests provide all of the documentation I need to understand the behavior of the method being tested. So the aforementioned unit test would be something like MakeSureNotAPrimeNumberExceptionIsThrownIfNumberisNotPrimeTest().

Class B knows that class C can throw a NotAPrimeNumberException. If I want to let the NotAPrimeNumberException bubble up out of class B should I write a unit test for class B to somehow check that a NotAPrimeNumberException is thrown in some circumstances? What about Class A? If A also lets the NotAPrimeNumberException bubble up, should it also have a unit test for this?

My concern is that if I DON'T write a unit test in Class B then the consumers for class B won't be aware that class B can throw this type of exception. However, if I DO write the unit test then it is a little silly that I have to force the call to class B to throw a NotAPrimeNumberException only to NOT handle the exception in class B. If I don't write a unit test, what is the appropriate way, if any, to document in the API that this exception can occur?

The bonus question is how do you facilitate this in NUnit with Rhino-mocks? This is of course dependent upon the first two questions.


Solution

  • If classes A and B are expecting certain behaviors from the classes the collaborate with, then you should test to ensure these behaviors occur. If you want to avoid the huge ball of mud that you are heading for by having hardwired calls from A to B to C then you should apply the Hollywood Principle / DIP in order to break this dependency. A should depend on an IB (interface of B) not on an instance of B. Then tests of A can simply assert that A does what it's supposed to do, including acting properly in the face of exceptions thrown by IB (or ignoring them). Likewise, B can depend on an interface of C, IC, and tests of B can similarly ignore the details of what C might or might not do, instead focusing only on what B does.

    An integration test that wires up A to B to C would be able to verify that the classes collaborate correctly, and would also be the place to ensure that an exception that is raised in C bubbles up from A if that is what you want to have happen. And if later it turns out that C simply returns null rather than raising an exception, then your integration test would fail and you would see that the new behavior had occurred. However, I wouldn't tend to clutter up my individual unit tests with tests showing that if things break the exception will bubble up since this is the default behavior. Instead I might test that any new exceptions I raise are thrown when expected, and that any handling of exceptions I perform works as I expect it to. This is because in a unit test you should only be testing your code under test - not the collaborators' behavior.