Throwing JDBC - java.sql error injector

This module is a reusable JDBC wrapper which provides a mechanism for Error Injection.

It enables testing of exception handling within a JDBC3 or 4 framework, without the use of mock objects by selectively throwing exceptions either whenever a method is called or after a given number of calls.

The JDBC API, especially version 4, is quite big, so hopefully sharing this module will save other people the wear and tear on their fingers.

The technique used could be applied to any interface, but is particularly suited to the JDBC API as it has a single entry point, the Driver, from which all other objects in the API are obtained.

Motivation

Test coverage is only worth having if it is complete because:

  • It is a pain to keep reviewing classes you can do no more about, if you get 100 percent coverage then they will drop off the todo list.
  • The coverage reports are only fun to read if there is a good reason for each non-covered line.
  • Automatons and managers only look at aggregate figures so covering trivia is good.
  • The bugs are only revealed in the last five percent of coverage testing.

How it works

Each interface within the API has a decorator whose constructor takes an instance. Any of the methods which return another instance of the API will now return a decorated instance.

The decorated instance can be told to throw an Exception, either whenever it is called or after being called a number of times. This enables you to cover cases that would otherwise be impossible to cover without a custom mock.

HSQLDB Throwing Driver

In the case of the JDBC API you can sub-class your Driver such that it returns a ThrowingConnection instead of a Connection, then use the ThrowingConnection as you would have the Connection.

import java.sql.Driver;

import org.hsqldb.jdbcDriver;
import org.melati.poem.dbms.test.sql.ThrowingDriver;

/**
 * A decorated Hsqldb jdbcDriver.
 */
public class HsqldbThrowingJdbcDriver 
    extends ThrowingDriver 
    implements Driver {

  public HsqldbThrowingJdbcDriver() {
    super(new jdbcDriver());
  }
}

Simple Example

You notice that there is uncovered Exception handling associated with failure of ResultSet.close() during database initialisation.

  public void testConnect() {
    ThrowingResultSet.startThrowing(ResultSet.class, "close");     
    try { 
      getDb();
      fail("Should have blown up");
    } catch (SQLSeriousPoemException e) {
      assertEquals("ResultSet bombed", e.innermostException().getMessage());
    }
    ThrowingResultSet.stopThrowing(ResultSet.class, "close");
  }

Sub-classed Test Example

The test is written to test the functionality in the normal way, then subclassed with a ThrowingConnection to test the exception handling.

public class org.melati.poem.test.throwing.DatabaseTest 
     extends org.melati.poem.test.DatabaseTest {
  public void testFirstObject() {
    ThrowingResultSet.startThrowing(ResultSet.class, "next");
    try { 
      super.testFirstObject();
      fail("Should have bombed");
    } catch (SQLSeriousPoemException e) { 
      assertEquals("ResultSet bombed", e.innermostException().getMessage());
    }
    ThrowingResultSet.stopThrowing(ResultSet.class, "next");
  }
}

Throw on third call Example

The exception handling you want to excercise is actually the third call to that method in your test's trajectory to the method under test.

  public void testGetObjectInt() {
    ThrowingConnection.startThrowingAfter(Connection.class,"prepareStatement", 2);
    try { 
      super.testGetObjectInt();
      fail("Should have blown up");
    } catch (SimplePrepareFailedPoemException e) { 
      assertEquals("Connection bombed", e.innermostException().getMessage());
    } finally { 
      ThrowingConnection.stopThrowing(Connection.class, "prepareStatement");
    }
}

Do send feedback to TimP at this address.