◆ Problem
You want to test your production code, verifying that it closes all the JDBC resources it allocates: result sets, statements, and connections.
◆ Background
The good news is that if your application moves all query execution into one place, just as we have recommended and described in this chapter, then there is not much work to do. The only production code that needs to clean up JDBC resources is that query execution code, so in this case you would only need to apply this recipe to a handful of methods.
344 CHAPTER 10 Testing and JDBC
The bad news is that if your application—and this is still the most common case—has JDBC calls all over the place, then you have much more work to do. You need to evaluate very carefully whether it is more effort to write all the tests you need or throw away all your data access code (but not the knowledge you gained in writing and reading it!) and replace it with the JDBC framework we have devel- oped here. Take some time and estimate—apply this recipe a few times and mea- sure how long it takes. Rewrite one data access class and measure how long it takes. Compare the results.
If you have decided to forge ahead and test all the scattered JDBC client code, rather than using a JDBC framework, then this recipe can point you in the right direction.
◆ Recipe
You can use the Mock Objects JDBC implementations to verify that close() has been invoked (or not, as the case might be) for the various JDBC resources you need to use. Listing 10.11 shown an example of such a test.
package junit.cookbook.coffee.jdbc.test;
import java.sql.*;
import junit.cookbook.coffee.data.*;
import junit.cookbook.coffee.data.jdbc.CatalogStoreJdbcImpl;
import junit.framework.*;
import com.diasparsoftware.java.util.Money;
import com.mockobjects.sql.*;
public class AddProductTest extends TestCase {
public void testHappyPathWithPreparedStatement() { Product toAdd =
new Product("999", "Colombiano", Money.dollars(9, 0));
final MockPreparedStatement addProductStatement = new MockPreparedStatement();
addProductStatement.addUpdateCount(1);
addProductStatement.setExpectedCloseCalls(1);
MockConnection2 connection = new MockConnection2();
connection.setupAddPreparedStatement(
"insert into catalog.beans "
+ "(productId, coffeeName, unitPrice) values "
+ "(?, ?, ?)", addProductStatement);
CatalogStore store = new CatalogStoreJdbcImpl(connection);
store.addProduct(toAdd);
Listing 10.11 Verify that the CatalogStore closes its PreparedStatement
Set expectation
345 Verify your production code
cleans up JDBC resources
addProductStatement.verify();
connection.verify();
} }
We have drawn attention to the code that sets and verifies our expectations, using a common Mock Objects coding pattern. First we invoke setExpectedCloseCalls() to indicate how many times the code we are testing should close the PreparedState- ment—once. At the end of the test we invoke verify() to allow both the mock Pre- paredStatement and mock Connection the chance to fail if the expectations we have set have not been met. That is, the test fails if we do not close the PreparedState- ment exactly once, or if we try to close the Connection at all.13 We do not want the CatalogStore to close the Connection for two reasons: first, whoever obtains the Connection ought to close it, and the CatalogStore did not obtain the connection;
and second, we want multiple data stores to be able to participate in the same trans- action, and to do that they must be able to use the same Connection, which means they had better not close it!
◆ Discussion
We have not included an example test that verifies that we have closed our ResultSet objects, but that is easy to add. Also remember that it is important to close the result set, and then the statement, and then the connection, in that order.
This technique does not ensure that we have cleaned up our resources in the order required: Mock Objects do not provide direct support for verifying the order in which methods on different objects have been invoked.
To be complete you need to write this kind of test for every distinct piece of JDBC client code. Think about how your JDBC client code is designed: you may need to write hundreds of tests. The good news is that if you notice coding patterns in the tests themselves, you can always refactor to a Parameterized Test Case (see recipe 4.8, “Build a data-driven test suite”) and, if the design contains actual dupli- cation, you may be able to extract a handful of representative test cases from your system and write just those tests. If you do not yet appreciate the power of refac- toring, you will once you get to avoid writing all those tests. Walk over to your manager and say, “I just saved us about 150 hours of work.” With luck, she will ask you how.
13Because we have not set an expected number of close() calls on the mock Connection, it expects close() not to be invoked at all. It is the same as invoking setExpectedCloseCalls(0).
Verify expectations
346 CHAPTER 10 Testing and JDBC
We see JDBC client code littered throughout applications on a regular basis and we view this as a serious design problem. You may have the sense that we look down on the people who create these designs problems, and that could not be further from the truth. If you are the one who wrote the data access code that led to having to write hundreds of tests like the ones in this recipe, do not feel bad about it. Instead, see how you can refactor your way out of it. You wrote the best code you could at that time under those conditions with what you knew then.
Don’t feel bad because you didn’t do what you did not know how to do. Who can?
Learn from the experience, and maybe laugh about it a little. We do.
◆ Related
■ 4.8—Build a data-driven test suite