When you’re writing software that interacts with another system, do you really know how that system behaves when it encounters errors? Do you really know what happens when you call IFooRepository.GetFoo(), which is supposed to return an object of class Foo, when the Foo doesn’t exist?
This situation happens a lot in programming. Let’s consider the most typical situations:
- GetFoo returns a null. I can check for null and handle the case when the requested Foo doesn’t exist.
- It throws an exception. No worries, I can catch the exception and do something to handle it.
- It returns a DefaultFoo – following a Null Object Pattern. Excellent! I can get what I want out of this foo by invoking a method polymorphically, without worrying about nulls and exceptions.
And every now and them, life throws you a curveball.
The repository does lazy load. No need to check for null – it is never null. No need to catch exceptions – they are never thrown. You always get a non-null Foo object. When you try Foo.Bar(), that’s when the data access layer actually goes to work and you get a FooBarException. You practice defensive programming, check for nulls, and all you get is this tech support ticket.
What To Do
- Know for sure the behavior of your system’s dependency (in the above example, the repository). If you’re writing it, use the same behavior consistently. Implement error cases test-first (expect null, expect exception, etc.)
- If you didn’t write the dependency, write a learning test around it. Set up a test that forces it to do something that indicates an error. Expect exception – aha, that’s not what it does! Now you know. If the system’s behavior changes in its next version you will know it as soon as your automated learning test fails.
- Write a unit-test using a stub. Simulate the dependency’s behavior in a stub object injected into your system under test. This assumes, of course, that its design follows SOLID Principles, in particular, the Dependency Inversion Principle.
- Use code coverage. If you see that exception catch blocks and code paths after checking for nulls are not covered, that may be a sign that they are dead code and do not handle actual errors.