Friday, May 07, 2010

Spring 2.5 Makes Unit Testing Easy with Annotations


One feature of Spring 2.5 (previously known as Spring 2.1) that hasn’t gotten a lot of mention is its significantly enhanced unit testing framework, including the brand new org.springframework.test.context package. Rod Johnson mentioned it in his updated article on Spring 2.5 on TheServerSide, but other than that, I tried searching for someone blogging about this subject, and failed – so I figured I’d jot down my impressions about this great improvement.

Prior to Spring 2.5, Spring provided a series of superclasses that your unit tests were required to inherit. These superclasses (AbstractDependencyInjectionSpringContextTests and the like) provided dependency injection into unit tests, and autowiring by type as default behavior. For those who are unit testing transactional systems, the AbstractTransactionalDataSourceSpringContextTests base class is essential for setting up transaction management and auto-rollback around each unit test.

Spring 2.5 continues this rich and convenient testing framework, while now removing the requirement that your unit tests extend Spring framework tests. This is a welcome change, as it frees up your unit test hierarchy to allow you more flexibility in inheritance.

Instead of providing functionality through subclassing and very specific method overriding, a series of Java 5 annotations are provided to handle various aspects of test data management.

As usual, the Spring documentation has excellent coverage of this new functionality, starting with its coverage of the test context concept, provided by the @ContextConfiguration annotation, which is used in place of the legacy method override of getConfigLocations.

To wit, where before, including several ApplicationContext XML files required the following:
    protected String[] getConfigLocations() {
    return new String[] { "classpath:applicationContext.xml",
        "classpath:applicationContext-test.xml" };

… with Spring 2.5 all that is required is a simple annotation at the unit test class level:
 @ContextConfiguration(locations = { "/applicationContext.xml",
    "/applicationContext-acegi.xml", "/applicationContext-test.xml" })

The default ContextLoader will automatically find these resources on the classpath.

Similar simplicity is provided in the realm of transactional support and DI. In prior versions of Spring, the superclass hierarchy dictated whether or not transactional and DI support was provided. In Spring 2.5, the default ContextLoader will also auto-wire dependencies in the test class, annotated via the typical @Autowired (by type) or @Resource (by name) methods.

For transaction management, the standard Spring @Transactional annotation is supported, to wrap transactions around unit test methods (and rolling back after method completion, as Spring 2.0 used to do). Spring 2.5 provides a couple additional annotations to further specify transactional behavior within a unit test, including the @NotTransactional annotation (for methods not requiring one), and the @Rollback annotation, which can be used to override the default auto-rollback behavior.

Finally, since you’re no longer extending a base class, you can’t override the onSetUp*Transaction and onTearDown*Transaction methods, like in Matt Raible’s example from way back when (among many others).

Instead, the standard JUnit 4 @Before and @After annotations are used in place of onSetUpInTransaction and onTearDownInTransaction. An obvious benefit is the fact that you can annotate multiple methods with these annotations, instead of the single method that was imposed by the override-based implementation. Spring-specific @BeforeTransaction and @AfterTransaction annotations are used to replace the onSetUpBeforeTransaction and onTearDownAfterTransaction methods.

There’s some additional annotations that allow for test timeout measurement (above and beyond what JUnit 4 provides), test repeating, and a few other goodies.

I tested this on a couple existing Spring projects that I’ve worked on, and the transition process was extremely smooth, and allowed me to greatly simplify the unit testing code by stripping out the stuff that used to be required by the Spring class-based unit testing framework.

Unfortunately, although Spring provides some support for TestNG, out of the box it will still require inheriting a [new] Spring 2.5 class, AbstractTransactionalTestNGSpringContextTests. Hopefully Cedric and Alexandru can work with the Spring team to enhance this support to match (or exceed!) the level of support offered to JUnit 4 users.

Spring 2.5 will be released soon, and I bet a lot of users will be buzzing about the unit test enhancements there. Enjoy!

No comments: