We still like Spring!

With all the recent Scala posts, you might think that we’ve abandoned our bread and butter, the Spring Framework.

Absolutely not!

Spring is still the most popular Java EE application framework; its dependency injection container allows us to combine the old-school XML with annotations and Java-based configuration. On top of the DI, there is the convenient support for dynamic AOP, data access; then we have all the enterprisey code (transaction management, scheduling, management, …). Top if off with excellent MVC implementation and in just the core of the framework, you have an excellent starting point for your applications! And that’s ignoring all the goodies in Spring Integration, Spring Batch, Spring Data, and many, many others.

But if you are keen to try something a bit more exotic, take a look at Specs2 Spring, which is the Spring Framework testing extension to the Specs2 Scala-based testing framework.

Spock Extension to simplify integration testing with Spring

The motivation for the post and the project is that most of my Spring test code was becoming too complex and–as a result–the test coverage was suffering. I was growing tired of explicit mocking and clumsy Java syntax. There had to be something better: Spock. But Spock was missing the full Spring integration I was looking for. No longer! My code at https://github.com/janm399/spock-spring-it fixes it and this post describes my motivation and the solution.

Most Spring enterprise applications use some DataSources, TransactionManagers and other JEE beasts. Now, we would like to use Spock to perform the necessary integration testing, but we don’t really want to create separate application context files for the tests.

Instead, we would like to set up the JNDI environment for the test code and use the same application context files for both testing and production. This is where this project helps: the annotations on our test classes specify the JNDI environment we wish to build for the test.

Verba docent, exempla trahunt, so I’ll start you off with a sample. Let there be:

public interface FooService {
	int x(String query);
}

@Service
@Transactional
public class DefaultFooService implements FooService {
	private HibernateTemplate hibernateTemplate;

	@Autowired
	public DefaultFooService(HibernateTemplate hibernateTemplate) {
		this.hibernateTemplate = hibernateTemplate;
	}

	public int x(String query) {
		Document d = new Document();
		d.setTitle("x");

		this.hibernateTemplate.saveOrUpdate(d);

		return query.length();
	}
}

To get this running, we give the META-INF/spring/module-context.xml configuration file:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="..."
	   xsi:schemaLocation="...">

	<context:component-scan base-package="org.spockframework.springintegration"/>

	<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/test" 
		expected-type="javax.sql.DataSource"/>
	<jee:jndi-lookup id="hibernateProperties" 
		jndi-name="java:comp/env/bean/hibernateProperties"
		expected-type="...HibernateProperties"/>
	<tx:jta-transaction-manager />
	<tx:annotation-driven />

	<bean id="sessionFactory" 
		class="org...hibernate3.AnnotationSessionFactoryBean">
		<property name="dataSource" ref="dataSource"/>
		<property name="packagesToScan">
			<list>
				<value>org...example.domain</value>
			</list>
		</property>
		<property name="hibernateProperties" 
			value="#{hibernateProperties.asProperties()}"/>
	</bean>

	<bean id="hibernateTemplate" class="org...hibernate3.HibernateTemplate">
		<property name="sessionFactory" ref="sessionFactory"/>
	</bean>

</beans>

This context file is the same for both tests and for production. The “variable” items (DataSource, TransactionManager and our custom HibernateProperties) beans are looked up from JNDI.

To the test, then. We have simply

@IntegrationTest
@ContextConfiguration(locations = "classpath*:/META-INF/spring/module-context.xml")
class FooServiceTest extends Specification {
	@Autowired
	FooService service

	def y() {
		expect:
		result == this.service.x(param)

		where:
		param	| 	result
		"one"	|	3
		"two"	|	3
		"four"	|	4
	}

}

If I wanted to have another integration test (perhaps testing another class), I would write:

@IntegrationTest
@ContextConfiguration(locations = "classpath*:/META-INF/spring/module-context.xml")
class SomeOtherServiceTest extends Specification {
	@Autowired
	SomeOtherService service

	def "testing service operation"() {
		expect:
		count = this.service.work(filter)

		where:
		filter	| 	count
		"Jan"	|	2
		"Ani"	|	1
		"Joe"	|	0
	}

}

The interesting part is the @IntegrationTest annotation. It is defined as

@Jndi(
	dataSources = @DataSource(name = "java:comp/env/jdbc/test",
		driverClass = JDBCDriver.class, 
		url = "jdbc:hsqldb:mem:test"),
	mailSessions = @MailSession(name = "java:comp/env/mail/foo"),
	transactionManager = 
		@TransactionManager(name = "java:comp/TransactionManager"),
	beans = 
		@Bean(name = "java:comp/env/bean/hibernateProperties", 
			type = HibernateProperties.class)
)
@Transactional
@TransactionConfiguration(defaultRollback = true)
@Retention(RetentionPolicy.RUNTIME)
public @interface IntegrationTest {
}

The reason why I have defined the @IntegrationTest annotation is because I want to use it on the two test classes: the FooServiceTest and SomeOtherServiceTest. Naturally, I could have used the @Jndi annotation on the test classes, but that would bring [too much] duplication.

The Spock extension understands the @Jndi annotation and its elements; it prepares the environment for the test and then executes the test in the usual Spock way.

In addition to the “webless” tests, I have now included Spring MVC web testing. This allows us to write simple
tests for our controllers; the test environment is as close to the real servlet container environment as possible. The web testing extension builds on the JNDI support and adds the @WebContextConfiguration annotation. Here is a simple
test code:

@Controller
public class IndexController {
	private ManagementService managementService;

	@Autowired
	public IndexController(ManagementService managementService) {
		this.managementService = managementService;
	}

	@RequestMapping(value = "/home/{name}", method = RequestMethod.GET)
	public String home(@PathVariable String name, Model model) {
		model.addAttribute("message", name);
		Message message = new Message();
		message.setSourceText(name);
		message.setProcessedText(name.toUpperCase());
		this.managementService.save(message);
		return "home";
	}

}

The Spock test for this simple controller is equally simple IndexControllerTest:

@WebTest
class IndexControllerTest extends WebSpecification {
	@Autowired
	def ManagementService managementService;

	def home() {
		when:
		def param = "hello"
		def wo = get("/home/%s", param)

		then:
		wo.modelAttribute("message") == param
		wo.html().contains param
		this.managementService.get(Message, 1L).sourceText == param
	}

}

The code is quite simple: we make a HTTP GET request to /hello/hello (which will execute the hello method of the IndexController). We assert that the model contains the expected attribute, that the generated HTML contains the model attribute and that the message has been persisted. To complete the picture, here’s the @WebTest annotation:

@Transactional
@TransactionConfiguration(defaultRollback = true)
@WebContextConfiguration(
	value = "/WEB-INF/sw-servlet.xml",
	contextConfiguration = 
		@ContextConfiguration("classpath*:/META-INF/spring/module-context.xml")
)
@Jndi(
	dataSources = @DataSource(name = "java:comp/env/jdbc/test",
			driverClass = JDBCDriver.class, url = "jdbc:hsqldb:mem:test"),
	beans = @Bean(name = "java:comp/env/bean/hibernateProperties", 
		type = HibernateProperties.class)
)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface WebTest {
}

Again, the intention of the @WebTest annotation is to allow me to re-use it on all web tests. Looking at its source, the most important annotation is the @WebContextConfiguration. It sets the name of the file from which we want to build the WebApplicationContext and sets the contextConfiguration: the location of the configuration files from which we will build the “server-side” part of our web application.

Building automatically

Now that you have the source code, we must configure Maven to include the Groovy compiler and to run all our Spock specifications as part of the tests. The configuration is actually quite simple: all we have to do is to include the appropriate build plugins. In fact, the entire portion of the build configuration is simply:

...

<build>
  <!-- Mandatory plugins for using Spock -->
  <plugins>
    <plugin>
      <groupId>org.codehaus.gmaven</groupId>
      <artifactId>gmaven-plugin</artifactId>
      <version>1.3</version>
      <configuration>
        <providerSelection>1.7</providerSelection>
      </configuration>
      <executions>
        <execution>
          <goals>
            <goal>compile</goal>
            <goal>testCompile</goal>
          </goals>
        </execution>
      </executions>
      <dependencies>
        <dependency>
          <groupId>org.codehaus.gmaven.runtime</groupId>
          <artifactId>gmaven-runtime-1.7</artifactId>
          <version>1.3</version>
          <exclusions>
            <exclusion>
              <groupId>org.codehaus.groovy</groupId>
              <artifactId>groovy-all</artifactId>
            </exclusion>
          </exclusions>
        </dependency>
        <dependency>
          <groupId>org.codehaus.groovy</groupId>
          <artifactId>groovy-all</artifactId>
          <version>1.7.5</version>
        </dependency>
      </dependencies>
    </plugin>
    <!-- Optional plugins for using Spock -->
    <plugin>
      <groupId>org.spockframework</groupId>
      <artifactId>spock-maven</artifactId>
      <version>${project.version}</version>
      <!-- you need to specify a concrete version number here -->
      <executions>
        <execution>
          <goals>
            <goal>find-specs</goal>
          </goals>
        </execution>
      </executions>
    </plugin>

    ...
  </plugins>
</build>

...

Downloads & Summary

So, get your hands on the source code at https://github.com/janm399/spock-spring-it: it is ready to be compiled and used in your projects. A proper documentation & downloads is coming soon to http://cakesolutions.org!

This entry was posted in Jan's Blog and tagged , , , . Bookmark the permalink.

2 Responses to We still like Spring!

  1. Pawan says:

    I am trying to run these tests in eclipse and getting below exception:

    java.lang.NoSuchMethodError: org/spockframework/util/ReflectionUtil.invokeMethodThatThrowsException(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
    at org.spockframework.spring.SpringTestContextManager.beforeTestClass(SpringTestContextManager.java:40)
    at org.spockframework.spring.SpringInterceptor.interceptSetupSpecMethod(SpringInterceptor.java:36)
    at org.spockframework.runtime.extension.AbstractMethodInterceptor.intercept(AbstractMethodInterceptor.java:34)
    at org.spockframework.runtime.extension.MethodInvocation.proceed(MethodInvocation.java:84)
    at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:176)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

    java.lang.NoSuchMethodError: org/spockframework/util/ReflectionUtil.invokeMethodThatThrowsException(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
    at org.spockframework.spring.SpringTestContextManager.afterTestClass(SpringTestContextManager.java:45)
    at org.spockframework.spring.SpringInterceptor.interceptCleanupSpecMethod(SpringInterceptor.java:89)
    at org.spockframework.runtime.extension.AbstractMethodInterceptor.intercept(AbstractMethodInterceptor.java:37)
    at org.spockframework.runtime.extension.MethodInvocation.proceed(MethodInvocation.java:84)
    at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:176)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

    Can you please help me in this.

  2. Jan Machacek says:

    I’ll need a little more information, preferably smallest possible project that demonstrates the problem. Use Pastebin or Gist or something similar & don’t forget to include the versions of all the JARs you’re using.

Leave a Reply