Thursday, December 18, 2008

Autowired JUnit Tests with Spring 2.5

Spring 2.5 ships with great support for integration testing through the classes in the org.springframework.test package. These classes allow you to dependency inject your test cases off of your existing Spring configuration, either by using your production Spring configuration file or one you've defined especially for the test case. This post explains how to annotate your JUnit 4 test cases to be autowired, but there's a lot more too the new spring-test.jar and it works with JUnit 3.8 also.

As setup for all the examples, let's assume there is a simple service that can return a String to you. Imaginative, huh?
public class MyService {

private final String name;

public MyService(String name) {
this.name = name;
}

public String getName() {
return name;
}
}
This service is defined in a Spring XML configuration file as:
    <bean id="myService" class="org.sample.MyService" >
<constructor-arg value="simple service" />
</bean>
To have Spring dependency inject our test case, we're going to have to annotate it with a little information. We'll use the standard JUnit @RunWith annotation to tell JUnit to use the Spring TestRunner, the Spring @ContextConfiguration annotation to indicate the TestCase needs injection, and the Spring @Autowired annotation to inject the MyService field by type.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public final class SimpleAutowireTest {

@Autowired
private MyService service;

@Test
public void testServiceName() {
assertEquals("simple service", service.getName());
}
}
There are a variety of ways to have Spring run your test. Besides SpringJUnit4ClassRunner.class there are also abstract TestCase objects you can inherit from, including some that will expose the ApplicationContext to you. By default, the spring configuration file for the test case will be [TestName]-context.xml. You can easily override this to point the test at your production configuration file. Also, the @Autowired annotation autowires the field by type. In the case of conflicts, you can specify the bean name using the @Qualifier annotation. Consider the following Spring configuration where there are two beans of type MyService in a file named applicationContext.xml:
    <bean id="deliveryService" class="org.sample.MyService" >
<constructor-arg value="delivery service" />
</bean>

<bean id="accountingService" class="org.sample.MyService" >
<constructor-arg value="accounting service" />
</bean>
The JUnit test now requires the @Qualifier field annotation and a configuration file location as an @ContextConfiguration parameter:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"applicationContext.xml"})
public final class AdvancedAutowireTest {

@Autowired
@Qualifier("deliveryService")
private MyService service;

@Test
public void testServiceName() {
assertEquals("delivery service", service.getName());
}
}
To note: if you point the test to the production Spring configuration, then you're running integration tests and not unit tests. Integration tests are valuable and necessary, but are not always a wholesale replacement of unit tests. There's still value in isolation testing each of the components with mock dependencies. What I like about using Spring for the integration tests is that it validates the Spring mapping at test time. On my projects, we occasionally have the unit tests all passing but the Spring configuration contains a circular dependency, and we don't find out until the build hits QA. A Spring integration test pushes up error time to the test phase, when a developer can take quick action and long before QA is held up.

As is standard with JUnit, each test method runs within its own instance of the TestCase. However, the Spring TestRunner will only create one ApplicationContext for all the instances of the test method. This means that if you're test method destroys the state of a singleton bean, then downstream tests methods using that bean will fail. The way to work around this is to annotate your method with the @DirtiesContext annotation. This tells the Spring TestRunner to reload the context between test methods. For instance, these tests will pass when annotated, but fail when not:
    @Autowired
private MyConfigurableService service;

@Test
@DirtiesContext
public void testServiceName1() {
assertEquals("configurable service", service.getName());
service.setName("Updating Name");
}

@Test
@DirtiesContext
public void testServiceName2() {
assertEquals("configurable service", service.getName());
service.setName("Updating Name");
}
There's a lot more in spring-test, but this is a good starting point. The transaction annotations look especially promising, including the ability to open and rollback transactions per method. The only reference I know for spring-test is the Javadoc at http://static.springframework.org/spring/docs/2.5.x/api/org/springframework/test/package-summary.html. Those looking for a Spring starter lesson might consult the DZone RefCard.

The dependencies for spring-test are: junit, spring-context, spring-core, spring-beans, spring-tx, and commons-logging. A sample Gradle script for your build is at href="http://svn.assembla.com/svn/SampleCode/spring-test/build.gradle.
All the code for this post (including Gradle build script and IDEA project) is posted at http://svn.assembla.com/svn/SampleCode/spring-test/ and can be downloaded through subversion with the following command:

svn co http://svn.assembla.com/svn/SampleCode/spring-test

8 comments:

Bob said...

Good post, Hamlet! One of the nicer features of the TestContext framework is its extensibility. All of its core functionality is delivered via implementations of TestExecutionListener. You can create custom listeners to add new functionality or incorporate the functionality of other tools.

I have an example of integrating DBUnit on Google Code you may be interested in:

http://code.google.com/p/springdbunit/

David Karr said...

The book "Pro Spring 2.5" has some information on these features and some examples.

KVictor said...

Thanks so much!
Information about @Qualifier tag was really useful!

Tero Keski-Valkama said...

Do you know if Spring can be used to configure the test instance with XML style explicit configuration?

For example, I might have a test class:

@RunWith(SpringJUnit4ClassRunner.cl
ass)
@ContextConfiguration("classpath:configuration.xml")
class Test {
private String parameter;
public void setParameter(String p) {
this.parameter = p;
}

@Test
public void testSomething() {
obj.method(parameter);
assert(...);
}
}

configuration.xml:

...





And this does not seem to work, because the test class instance that the Spring JUnit Runner runs, is not configured itself from the given XML file.

This type of functionality is very important when you read application configuration parameters from properties files using Spring, and need these same values in the unit tests also.

Tero Keski-Valkama said...

Sorry, my message was sanitized of XML, but the gist of the matter was in the Java-code.

How would it be possible to configure the test instance automatically as an explicitly (through XML) configured Spring bean using the SpringJUnit4ClassRunner?

w0nder said...

Really usefull information :) Thanks for this post!

Igosuki said...

Even in 2011 with Spring 3 this post is very useful.
I'm using Easymock to catter for the DataSource and of course I need to reload the context.

Sindu S said...

Hi, how to write the junit test cases for the spring annotation classes involved autowired dependencies that requests from action class to business delegates method and from there to jpa methods.
I cannot create the object for springbean annotaiton classes.so can you please provide me solution