I recently had the task of writing an integration test utility that would setup a complex Java Content Repository (JCR) for use by integration tests. This turned out to be much more fun than I thought all because of Spring
. As I’ve worked with Spring over the years, I’ve found that staying in of the normal mode of declaring Spring xml files and letting it rule your application works great. However, once you start treating Spring like a 2nd class citizen and telling it what to do, it will make your life pretty difficult. One thing I will say though, is just about anything is possible; you will just have to work at it. In my case, trial, error, and luckily happening upon useful javadoc eventually got me the answer.
Here is what I set out to do…
- I needed to control the loading of Spring in an encapsulated way, inside my utility class. I did not want to require every JUnit test that uses my utility to have to annotate with a @RunWith(SpringJUnit4ClassRunner.class) and a @ContextConfiguration(locations = { “classpath:/some.spring.xml” }). I wanted the JUnit test writer to not care about how the JCR subsystem was configured and started.
- The test utility needs to be able to read more than one Spring beans xml file for configuration. Two Spring beans files are required to configure the test JCR environment: the production-ready one, plus one with a few overrides specific to the test environment.
- Lastly, and the requirement that was the most difficult to get working, I needed to programmatically configure/override one of the beans that was declared prior by one of Spring xml files. Spring was managing the jcrRepository factory bean, which writes data to a specified dir on your filesystem. In my integration test environment, I needed that folder to be unique for each test run. That’s why declaring the “home” dir in a Spring xml file just wouldn’t do. The test utility needed to generate a unique dirname.
After many different attempts at using ApplicationContexts and BeanFactories in various interesting ways, I came upon the answer. DefaultListableBeanFactory is the best suited Spring bean factory for complex configuration. Once I discovered it’s ability to both programmatically define beans *and* read in more than one Spring xml bean file, the problem was solved. Here is a snippet showing how, in my test utility class, I was able to instantiate a DefultListableBeanFactory. The code illustrates using XmlBeanDefinitionReader to configure beans based on Spring xml files, then how to programmatically define an additional bean (with the unique dir name as a property).
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader bdr = new XmlBeanDefinitionReader(beanFactory);
bdr.loadBeanDefinitions(new ClassPathResource("repository.spring.xml"));
bdr.loadBeanDefinitions(new ClassPathResource("repository-test-override.spring.xml"));
BeanDefinitionBuilder beanBuildr = BeanDefinitionBuilder
.rootBeanDefinition("org.springframework.extensions.jcr.jackrabbit.RepositoryFactoryBean");
beanBuildr.setScope(BeanDefinition.SCOPE_SINGLETON);
beanBuildr.addPropertyValue("configuration", new ClassPathResource("/jackrabbit-test-repo.xml"));
beanBuildr.addPropertyValue("homeDir", new FileSystemResource(tempDir));
beanFactory.registerBeanDefinition("jcrRepository", beanBuildr.getBeanDefinition());