We’re using Apache Jackrabbit for one of the JCR implementations in our unit tests. Configuring Jackrabbit isn’t intuitive at first (like many libraries, it’s highly configurable and thus non-trivial to configure), so the trick for us was figuring out how we wanted to use it in our unit tests.
One of the more important qualities of a unit test is that its fast. We do a lot of unit testing, and so we run unit tests very frequently. Change, compile, run tests. Repeat. Repeat again, and again. So the slower the tests take to run, the more they interrupt this process and your train of thought. (More on our testing philosophy and techniques in a future post.)
So we’ve found that the easiest way to speed up Jackrabbit is to use the in-memory persistence manager and the in-memory file system implementations. Here’s a snippet of the XML configuration showing the in-memory file system for the “/repository” branch:
<filesystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem"> <param name="path" value="${rep.home}/repository"/> </filesystem>
and here’s a snippet showing the XML configuration for the in-memory persistence manager:
<PersistenceManager class="org.apache.jackrabbit.core.persistence.mem.InMemPersistenceManager"> <param name="persistent" value="false"/> </PersistenceManager>
Remember, there are two persistence managers and three file system managers in the normal configuration, so make sure to change all of them.
Then in your test code, create an instance of the TransientRepository class by passing in the location of your configuration file and the location of the directory used for the repository data. We’re using Maven 2, so our configuration file goes in “./src/test/resources/” while we use “./target/testdata/jackrabbittest/repository” for the test data directory.
We’re also using JUnit (version 4.4), so one decision we had to make was whether to set up the repository in a @Before method and tear it down in an @After method. This makes all the tests easy to write, but it also means that the repository is set up and torn down for every test case. That means slower than necessary. And since I like to have a single test class for each class, my test cases often have a mixture of test methods that need a repository and test methods that don’t.
The pattern we’ve settled on is to create an abstract base class that sets up the repository in a “startRepository()” method, and in the @After method automatically tear it down if needed. That means in our unit test case classes that use Jackrabbit, simply extend the base class, and call “startRepository()” in those test methods that need the repository. Test methods that don’t need a repository don’t take the time to set it up. Plus, I personally like that this explicit call makes it more obvious which test needs the repository.
There’s one final twist. The TransientRepository cleans itself up when the last session is closed (not when the instance is garbage collected). Since some tests try saving saving changes in a session, closing the session and opening a new one can make all this data go away. To fix this, our “startRepository()” method creates a “keep alive” session, and our @After tear down method closes the session if it’s there.
Here’s the basics of our abstract base class:
private static Repository repository;private Session keepAliveSession; @BeforeClass public static void beforeAll() throws Exception { // Clean up the test data ... FileUtil.delete(JACKRABBIT_DIRECTORY_PATH); // Set up the transient repository (this shouldn't do anything yet)... repository = new TransientRepository(REPOSITORY_CONFIG_PATH,REPOSITORY_DIRECTORY_PATH); } @AfterClass public static void afterAll() throws Exception { try { JackrabbitRepository jackrabbit = (JackrabbitRepository)repository; jackrabbit.shutdown(); } finally { // Clean up the test data ... FileUtil.delete(JACKRABBIT_DATA_PATH); } } public void startRepository() throws Exception { if (keepAliveSession == null) { keepAliveSession = repository.login(); } } @After public void shutdownRepository() throws Exception { if (keepAliveSession != null) { try { keepAliveSession.logout(); } finally { keepAliveSession = null; } } }
So setting up unit tests is a piece of cake, and they run very quickly. Now we’re getting somewhere.
Filed under: techniques, testing, tools