Error Injection

We can use injection to introduce the data of our choice for testing our software but we can also use it to force error conditions. Although best practice suggests that we keep our methods as small and as simple as possible, there are many times when the tradeoff against design complexity leads to a little more complexity in a method. Consider the hypothetical code to retrieve a response with a proprietary network protocol shown in Listing 8-6.

Listing 8-6: Typical management of connection-oriented protocol in Java

public class NetRetriever {
  public NetRetriever() {
  }

  public Response retrieveResponseFor(Request request)
      throws RetrievalException {
    try {
      openConnection();
      return makeRequest(request);
    } catch(RemoteException re) {
      logError("Error making request", re);
      throw new RetrievalException(re);
    } finally {
      closeConnection();
    }
  }
}

This kind of logic is typical of many connection-oriented protocols. In fact, it is a rather simple variation on one. For example, if you were retrieving a result set from a prepared statement using JDBC for database connections, you might have several things to clean up, including the result set object, the prepared statement object, the database connection, and possibly a transaction with commit or rollback paths.

Even in this example, several consequences of an exception would prove difficult to verify with the current code. As shown, there is no way to verify that the connection was cleaned up properly. Without knowing the implementation details of the logging, we do not know how easily we can verify the logging behavior.

But for the purposes of demonstrating error injection, let’s look at what we can easily verify. We know that if the makeRequest() call throws a RemoteException, the method should throw an instance of RetrievalException wrapped around it. Listing 8-7 shows how such a test might look.

Listing 8-7: Testing the error conditions of Listing 8-6

@Test(expected = RetrievalException.class)
public void testRetrieveResponseFor_Exception()
    throws RetrievalException {
  NetRetriever sut = new NetRetriever() {
    @Override
    public Response makeRequest(Request request)
        throws RemoteException {
      throw new RemoteException();
    }
  };

  sut.retrieveResponseFor(null);
}

A couple of things might look strange here, especially if you aren’t as comfortable with Java. First, the expected attribute of the Test annotation indicates that JUnit should only consider this a passing test if a RetrievalException is thrown. The lack of an exception or the throwing of any other exception constitutes a test failure. This allows us to specify that we expect an exception in a very streamlined way without elaborate try/catch logic cluttering the test. It has the limitation that it cannot verify anything about the exception, but for our purposes right now it suffices.

The construct we use to create our test object is called an anonymous inner class. It creates a class derived from NetRetriever that overrides the makeRequest() method. In a language like C++, we could create an inner or nested class for this purpose, but it would require a type name.

Finally, we do not capture the return of retrieveResponseFor() and we pass null to it. Neither one matters for our test’s purposes. When a method throws an exception, it does not return a value; we are overriding the method to throw the exception from the method that would normally handle request. Our tests are only as elaborate as needed.

Using overrides to inject errors greatly expands the potential for our testing. Often, error handling code is the least thought out and least tested aspect. However, the grace with which your application handles errors and preserves a customer’s work can make or break your reputation. Testing these error paths gives us an avenue to win the hearts and minds of our customers, even in the face of inevitable programming mistakes.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.91.254