6.7 Breaking Up Methods to Avoid Mock Objects

6.7.1 Problem

You want to test a method without resorting to the complexity of mock objects.

6.7.2 Solution

Split the method into smaller pieces, ensuring that each piece performs one task. Small, single-purpose methods improve code quality in addition to making them testable.

6.7.3 Discussion

Example 6-12 shows a method that is hard to test. It is hard because you must create a mock ResultSet implementation in order to write your tests.

Example 6-12. Hard to test
// fetch an account type code from the database and convert it
// into one of the Account constants
int getAccountType(ResultSet rs, String acctTypeColName)
        throws SQLException, DataSourceException {
    String acctStr = rs.getString(acctTypeColName);
    if ("SA".equals(acctStr)) {
        return Account.SAVINGS;
    }
    if ("CH".equals(acctStr)) {
        return Account.CHECKING;
    }
    throw new DataSourceException("Unknown account type: " + acctStr);
}

The fundamental problem is that this method performs two tasks, rather than one. It is also a little messy because it throws two types of exceptions. The first task is to retrieve data from the ResultSet. The second task is to convert that data into some other form.

When confronted with a method like this, do not try to write a sophisticated unit test. Instead, first try to simplify the method. Example 6-13 shows a simplified version of this method. It is now assumed that the caller obtains the account code from the database before calling this method, whose sole purpose is converting that string into a Java constant.

Example 6-13. The same logic, now testable
// convert a database account code, such as "CH", into a Java constant
int getAccountType(String acctTypeStr)
            throws DataSourceException {
    if ("SA".equals(acctTypeStr)) {
        return Account.SAVINGS;
    }
    if ("CH".equals(acctTypeStr)) {
        return Account.CHECKING;
    }
    throw new DataSourceException("Unknown account type: " + acctTypeStr);
}

You can now test this method without resorting to mock objects. We also eliminated the extra SQLException because we no longer use JDBC in this method. Example 6-14 shows the test.

Example 6-14. Test for the getAccountType( ) method
public void testGetAccountType(  ) throws Exception {
    AccountFactory acctFact = new AccountFactory(  );
    assertEquals("account type", Account.CHECKING,
                 acctFact.getAccountType("CH"));
    assertEquals("account type", Account.SAVINGS, 
                 acctFact.getAccountType("SA"));
    try {
        acctFact.getAccountType("bogus");
        fail("Expected DataSourceException");
    } catch (DataSourceException expected) {
    }
}

6.7.4 See Also

This method was taken from Example 6-8 earlier in this chapter.