BeanInitializer: wiring dependencies in unit tests

One of the things that irritates me the most about unit testing some classes in a Spring context, is initialising them with all their dependencies. This is especially true of Spring framework extensions, like FactoryBean implementations or *Aware implementations. It is cumbersome to add all the dependencies, and easy to forget to call the bean lifecycle methods, like the afterPropertiesSet method from InitializingBean.
The Spring base classes for unit testing help quite a lot, but there are still some things that are fiddly. E.g. in many cases it is necessary to disambiguate autowiring, so that collaborators are given the correct implementation. Also, to benefit from the lifecycle execution you have to be testing a bean instance from the current configuration, which isn't always convenient.
I have been using a simple tool for simplifying the setup of collaborators in unit tests, and I thought I'd share it with a few people. It provides a bean initializer that can be used to wire up dependencies on an existing bean.
The BeanInitializer initializes a bean, adds dependencies, and executes lifecycle callbacks all in one method:
private Collaborator collaborator;
public void setUp throws Exception {
super.setUp();
collaborator = new SimpleCollaborator();
}
public void testBeanWithSimpleDependencyOnThis() throws Exception {
Service bean = BeanInitializer.initialize(new ServiceImpl(), this);
assertNotNull(bean.getCollaborator());
}
}
The parameters of the BeanInitializer.initialize methods are the bean to initialize, and a source of properties to set on it, in this example the unit test itself. The lifecycle callbacks from InitializingBean, BeanNameAware etc. are called before returning the bean. This is useful because the lifecycle interfaces can be added or removed without having to change the unit test.
You can also do this:
or this:
Object[] { new Collaborator(), "valueOfOnlyStringProperty" } );
or this (e.g. in a unit test using the spring-mock base classes):
new Collaborator(), applicationContext );
In this last case, the explicit collaborator overrides any values with the same interface in the applicationContext, so autowiring is unambiguous. This is great for adding a mock implementation of a complicated service class where the application context already includes a "real" implementation. Remember that the Spring unit test base classes cache application context instances, so this can be a really great way to improve unit test efficiency.
I've uploaded the code if you are interested (here) - it's pretty simple, but quite useful, hopefully.
Debasish Ghosh says:
Added on April 2nd, 2007 at 11:43 pmCan't find the uploaded code link ..
Dave Syer (blog author) says:
Added on April 3rd, 2007 at 2:04 amHmm. I uploaded a file, but it doesn't show on the main blog page. I'll look into it. Meanwhile you can get the same file from here: http://static.springframework.org/downloads/bean-initializer.zip
Rick says:
Added on April 3rd, 2007 at 7:11 amHi Dave
Good post. I would make the small observation that the BeanInitializer is suited for a specific subset of tests, and typically should not be used for the majority of the unit tests in one's code base (not that I think you were suggesting that, I just want to make that clear).
To my mind, the majority of the *unit* tests in one's code base are still going to use plain old Java constructs (indeed, plain old OO constructs) such as constructors and setters to supply dependencies, mock or otherwise, to the unit under test (rather fortuitously, I recently blogged about just this topic). There is no need to involve Spring at this point, because the majority of the classes in a well structured (Di-based) enterprise application are going to have no Spring dependencies at all. Where the BeanInitializer class, and the other support classes in the Spring mock library, provide tangible value is in the realm of *integration* tests; here the Spring support classes, such as the AbstractDependencyInjectionSpringContextTests class, allow one to use Spring to test (well, obviously I guess) the wiring up of all the various interdependent classes in the integration test in a very convenient fashion (and of course the logic too!). The BeanInitializer also has it's place in integration tests for those special cases where one needs to plug in a non-standard implementation of a collaborator to simulate a failure scenario.
The BeanInitializer does have it's place in a small subset of unit tests, specifically those where you absolutely would benefit from using it
(haha), or are tied to the framework anyway because one is testing a FactoryBean (for example).
Cheers
Rick