Fork me on GitHub

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");
    }
}

JDBC3, JDBC4 and JDK7 compatibility

The java.sql API has changed quite a bit. There were many new methods added in JDBC4. This library produced two different out put jars.

The JDK7 java.sql breaks backwards compatibility by adding new methods and and one new class. As Oracle put it in their Incompatibility notes:

RFE: 2164549

Area: API: JDBC
Synopsis: New RowSetFactory Interface to allow Creation of a RowSetFactory
Description: New API was introduced to support RowSet 1.1 and, specifically, the ability to write more portable code by creating a RowSetFactory. As part of this update, the definition of some constants has changed slightly, but should not affect most users.
Nature of Incompatibility: source

RFE: 6843995

Area: API: JDBC
Synopsis: New JDBC Methods, Including new Methods in Interfaces
Description: For the Java SE 7 release, there are new methods to support JDBC 4.1. This includes methods added to the java.sql.Connection, java.sql.Driver, javax.sql.CommonDatasource, and java.sql.Statement interfaces. Because all methods of an interface must be implemented, previous code that uses these interfaces will not compile on Java SE 7 unless you add the new methods. See the JDBC documentation for more information.
Nature of Incompatibility: source

Do send feedback to TimP at this address.