In my opinion, one of the most underutilized of the common design patterns is the Null Object pattern.3 The concept is simple. Instead of returning the ubiquitous null
(or its various equivalents such as undef
, nil
, or NULL
) and testing for it as a special value, we return an object of the expected type that implements the behavior associated with an unknown, missing, or indistinct value. Several such objects may reasonably exist for a given situation, giving rise to Martin Fowler’s Special Case pattern4 as a generalization.
3. Surprisingly to many, this is not one of the original Gang of Four design patterns but was first proposed in the Pattern Languages of Program Design series. Martin Fowler [REF], Josh Kerievsky [RTP], and Robert Martin [CC08] all use it significantly.
4. http://martinfowler.com/eaaCatalog/specialCase.html
Null object implementations are particularly useful in testing a stub. Take, for example, some code that passes an interface or abstract class as a parameter. What if the Connection
used in Listing 8-7 were an interface instead of a class? To override even a single method we would have to implement all of the methods. In some languages, we could use a mocking framework like JMock, EasyMock, or Mockito in Java. Let’s examine a technique we can use in almost all languages.
One can easily imagine NetRetriever
as part of an overall application that could perhaps manage multiple connections. And this application might have the concept of a current connection, at least for the purposes of its user interface. How might one implement the current connection before an actual connection is chosen or even defined? Rather than use null
and test for that special value everywhere as in Listing 8-10, let’s create a NoConnection
class that automatically initializes the current connection. This class would look something like Listing 8-11.
if (currentConnection == null) {
throw new InvalidConnectionException();
}
currentConnection.doSomething();
class NoConnection implements Connection {
@Override
public void open() throws RemoteException {
throw new InvalidConnectionException();
}
@Override
public void close() {
// Who cares if we close a nonexistent connection?
return;
}
@Override
public void doSomething() throws RemoteException {
throw new InvalidConnectionException();
}
...
}
Now the conditional test for currentConnection
’s null-ness can be removed. The NoConnection
class behaves as one would expect a lack of connection to behave. In fact, in our example, it would behave much as a misbehaving connection might. Not only would this simplify our overall application code, it would also reduce the possibility of the dreaded NullPointerException
that plagues many applications.
But there is an additional benefit for our purposes in writing tests. We now have a complete implementation of the interface as the basis for our injected objects. Using this null object implementation, the test from Listing 8-9 would use NoConnection
as a stub (Listing 8-12).
@Test(expected = RetrievalException.class)
public void testRetrieveResponseFor_Exception()
throws RetrievalException {
Connection connection = new NoConnection() {
@Override
public void open() throws RemoteException {
throw new RemoteException();
}
};
NetRetriever sut = new NetRetriever(connection);
sut.retrieveResponseFor(null);
}
In fact, if InvalidConnectionException
is derived from RemoteException
as you would surmise from the throws
clause in the method signature, we would not even need to override the open()
method, simplifying our test to that in Listing 8-13. You could also derive it from RuntimeException
, but should only do so if throwing a RuntimeException
is a valid expectation in the context. Nothing in this context indicates that it is anticipated.
@Test(expected = RetrievalException.class)
public void testRetrieveResponseFor_Exception()
throws RetrievalException {
Connection connection = new NoConnection();
NetRetriever sut = new NetRetriever(connection);
sut.retrieveResponseFor(null);
}
While this is a powerful technique, we should also be aware that it introduces coupling between our test and an additional production class. However, in the case of null object implementations, that coupling tends to be of little consequence.
3.142.199.181