Home > spock-spring-it

spock-spring-it

Spock-spring-it is a project mainly written in JAVA and GROOVY, it's free.

Spring integration test support for Spock

Spock Extension to simplify integration testing with Spring.

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 simple 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.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="packagesToScan">
        <list>
            <value>org.spockframework.springintegration</value>
        </list>
    </property>
    <property name="hibernateProperties" value="#{hibernateProperties.asProperties()}"/>
</bean>

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

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";
}

}

@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.