◆ Problem
You want a simple way to manage a resource that all your tests share—such as a database— without duplicating custom lookup code throughout your tests.
◆ Background
Duplication is the root of all evil in programming. Expensive, external resources make Object Testing difficult. Put the two together...well, we would rather not put the two together. Duplicating expensive, complex code is about as bad as it gets in object-oriented design, and it is entirely avoidable. If you use a testing resource (a data source, a JNDI directory, or some data files) in multiple test case classes, and if you refactor mercilessly the code that gains access to those resources, then you can end up with a design very similar to the solution we propose in this recipe. If that is true, then you might as well just follow this recipe and cut out the middle man.
◆ Recipe
There is one straightforward solution: use a central Registry [PEAA, 480] of resources. There are essentially two ways to use a Registry: either register a resource the first time you use it or register all the resources you need in one big TestSetup that you execute for your entire test suite (see recipe 5.10, “Set up your fixture once for the entire suite”). There are good and bad points with each.
■ Register each resource as you use it—This makes it easy to start using new resources; however, it can make it more difficult to understand where objects are coming from. If a test dies because of a resource problem, you have to search all the tests to find the test suite that initializes that resource.
■ Register all resources in one TestSetup wrapper—This solves the key problem with the other approach by keeping all your shared resources in one place;
however, you need to wrap any test suite you execute in this Resource- TestSetup, otherwise the tests fail. This generally requires a customized test execution script and might be incompatible with IDE-based test runners such as the one in Eclipse.
Whichever approach you take, JUnit-addons provides the class junitx.util.
ResourceManager to help you manage your test sources. The class itself is simple enough: it is a collection of named resources, and you have access to the usual operations: add, remove, get and contains. Any kind of object can be a resource.
594 CHAPTER 16 JUnit-addons
Listing 16.5 shows an example of using a DataSource as a resource, adapted from recipe 10.10, “Test legacy JDBC code with the database.” We opt for the second approach for initializing the ResourceManager. We have highlighted our use of the ResourceManager in bold print.
package junit.cookbook.addons.jdbc.live.test;
import java.io.File;
import java.sql.*;
import java.util.Set;
import javax.sql.DataSource;
import junit.cookbook.coffee.data.CatalogStore;
import junit.cookbook.coffee.data.jdbc.CatalogStoreJdbcImpl;
import junit.framework.*;
import junitx.util.ResourceManager;
import org.dbunit.DatabaseTestCase;
import org.dbunit.database.*;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.xml.FlatXmlDataSet;
import com.diasparsoftware.jdbc.JdbcResourceRegistry;
public class FindProductsTest extends DatabaseTestCase { private JdbcResourceRegistry jdbcResourceRegistry;
public FindProductsTest(String name) { super(name);
}
public static Test suite() {
return new ResourceManagerTestSetup(
new TestSuite(FindProductsTest.class));
}
protected void setUp() throws Exception {
System.setProperty("dbunit.qualified.table.names", "true");
jdbcResourceRegistry = new JdbcResourceRegistry();
super.setUp();
}
protected void tearDown() throws Exception { jdbcResourceRegistry.cleanUp();
super.tearDown();
}
public void testFindAll() throws Exception { Connection connection = makeJdbcConnection();
CatalogStore store = new CatalogStoreJdbcImpl(connection);
Set allProducts = store.findAllProducts();
assertEquals(3, allProducts.size());
}
Listing 16.5 Using ResourceManager to manage a DataSource
595 Manage shared test resources
public void testFindByName() throws Exception { Connection connection = makeJdbcConnection();
CatalogStore store = new CatalogStoreJdbcImpl(connection);
Set allProducts = store.findBeansByName("Sumatra");
assertEquals(1, allProducts.size());
}
private DataSource getDataSource() {
return (DataSource) ResourceManager.getResource("dataSource");
}
private Connection makeJdbcConnection() throws SQLException { Connection connection = getDataSource().getConnection();
jdbcResourceRegistry.registerConnection(connection);
return connection;
}
protected IDatabaseConnection getConnection() throws Exception { Connection connection = makeJdbcConnection();
return new DatabaseConnection(connection);
}
protected IDataSet getDataSet() throws Exception { return new FlatXmlDataSet(
new File("test/data/datasets/findProductsTest.xml"));
} }
As you can see we did not make any significant coding changes to introduce the ResourceManager, so you ought to have little trouble incorporating it into your project. For completeness, we show our ResourceManagerTestSetup in listing 16.6.
package junit.cookbook.addons.jdbc.live.test;
import junit.cookbook.coffee.jdbc.test.CoffeeShopDatabaseFixture;
import junit.extensions.TestSetup;
import junit.framework.Test;
import junitx.util.ResourceManager;
public class ResourceManagerTestSetup extends TestSetup { public ResourceManagerTestSetup(Test test) {
super(test);
}
protected void setUp() throws Exception { ResourceManager.addResource(
"dataSource",
CoffeeShopDatabaseFixture.makeDataSource());
}
Listing 16.6 ResourceManagerTestSetup
596 CHAPTER 16 JUnit-addons
protected void tearDown() throws Exception { ResourceManager.removeResource("dataSource");
} }
This is all it takes to get started using the JUnit-addons ResourceManager, and as you can see, it is quite handy, and you can register as many resources as your tests need.
For testing against a live database—if you must—the combination of Resource- Manager and DbUnit is quite powerful.
◆ Discussion
Now because we only have one test suite that uses the ResourceManager, we wrapped it directly in a ResourceManagerTestSetup to execute it. Without ResourceManagerTest- Setup, any invocation of getResource() returns null. When we add a second test suite that uses the ResourceManager, we need to ensure that it too executes within a ResourceManagerTestSetup. How to do this depends on the environment.
If you use Eclipse and execute your tests with the built-in test runner, you have a few options, none of which are encouraging. You could create a suite() method in each test case class that wraps itself in a ResourceManagerTestSetup; you could cre- ate an AllTests class (see recipe 4.3, “Collect all the tests in a package”) and wrap it in a ResourceManagerTestSetup; or you could use the register-as-you-go design, but then each test would need to be able to register the resource it needs in case you do not execute the test that initializes the resource for you. Looking for some- thing even better than the best of these options leads you in the direction of using the JUnit-addons test runner, which we describe towards the end of this recipe.
If you use Ant and <batchtest> to collect all the tests in your source tree, then you have at least two options. You can extend the <batchtest> task to wrap a ResourceManagerTestSetup around the suite it would otherwise collect. If you do this, please publish it as open source, because the community could certainly use it. If you do not want to learn about Ant tasks at this moment, you can convert your
<batchtest> task into to a special AllTests class. This class’s suite() method just wraps a ResourceManagerTestSetup around a suite collected using DirectorySuit- eBuilder. If you also use <junitreport> to report your test results, then you can duplicate <batchtest>’s XML output by writing a custom XML-based TestListener and registering it to the JUnit-addons test runner. After some thought, perhaps the custom Ant task is easier! Do whichever makes you more comfortable.
Finally, you should consider using JUnit-addons test runner, which provides a natural integration for the ResourceManager. No surprise there, as they came from
597 Ensure your shared test fixture
tears itself down
the same project! You can let this custom test runner manage your resources auto- matically by doing the following:
1 Create a resource wrapper class that implements junitx.runner.Resource. This is a resource factory class.
2 Override Resource.init() to initialize your resource.
3 Add an entry to test.properties such as junitx.resource.1=com.mycom.
MyResourceFactory.
The number at the end of the property name controls the order in which the resources are initialized. Now when you execute any test suite with the JUnit- addons test runner and this test.properties file, the test runner manages your resources and you can obtain them using ResourceManager. If you have more than two or three resources to manage, we highly recommend this last approach.
◆ Related
■ 4.3—Collect all the tests in a package
■ 5.10—Set up your fixture once for the entire suite