SQL Zone is brought to you in partnership with:

Graeme has posted 1 posts at DZone. View Full User Profile

Using Groovy & BeanBuilder for Spring + Hibernate Integration Testing

03.12.2008
| 16969 views |
  • submit to reddit

It is already pretty widely known how great Groovy is for writing unit tests for your Java code. Thanks to having first class constructs for lists, maps and ranges combined with built in support for mocking using Groovy for your unit testing is a great way to introduce Groovy into a Java codebase.

However, integration testing is a different kettle of fish and, in the case of Spring+Hibernate, often involves a combination of in-memory databases, Spring containers and so on. The traditional way to perform integration testing in Spring is to extend something like AbstractDependencyInjectionSpringContextTests and then override the getConfigLocations method to supply a static Spring XML configuration to use. For example here is a snippet from one of Grails' tests that uses this technique:

     protected String[] getConfigLocations() {
return new String[] { "org/codehaus/groovy/grails/orm/hibernate/hibernate-mapped-class-tests.xml" };
}

 

The Spring XML would do the job of setting up any singleton beans that your classes depend on such as a LocalSessionFactoryBean for Hibernate, the in-memory DataSource using something like HSQLDB and so on. However, all of this is rather tedious as you then have to maintain a separate XML file from your test and, worse, make sure its on the classpath in your build and in your IDE when running the test. The result is rather painful.

The good news is with Grails' BeanBuilder there is a better way! For those of you who don't know BeanBuilder is a DSL for Spring that allows you to construct an ApplicationContext on the fly using a Groovy builder like syntax. We're going to use it to construct an ApplicationContext to use in an integration test involving Hibernate.

The first thing we need to do is create some Hibernate entities. Since we're already using Groovy we may as well write those in Groovy as the syntax is a little more terse:

package com.mycompany
import javax.persistence.*
import org.hibernate.annotations.*

@Entity
@Table(name="faq_section")
class FaqSection
{
@Id
@GeneratedValue(strategy = javax.persistence.GenerationType.IDENTITY)
Long id

@Version
Long version

String title

@OneToMany(cascade = [javax.persistence.CascadeType.ALL], targetEntity = FaqElement.class)
@JoinColumn(name = "section_id", nullable = false)
@IndexColumn(name = "pos", base = 0)
List elements
}

@Entity
@Table(name="faq_element")
class FaqElement
{
@Id
@GeneratedValue(strategy = javax.persistence.GenerationType.IDENTITY)
Long id

@Version
Long version

String question
String answer

@ManyToOne
@JoinColumn(name = "section_id", nullable = false, updatable = false, insertable = false)
FaqSection section

}

Besides the JPA annotations which are a bit of eyesore, the code itself is pretty concise and just defines 2 entities that have a bidrectional one-to-many association between each other using a List collection. Once that is done we can define the Hibernate mapping for these:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

<!-- a SessionFactory instance listed as /jndi/name -->
<session-factory>
<!-- mapping files -->
<mapping class="com.mycompany.FaqSection" />
<mapping class="com.mycompany.FaqElement" />
</session-factory>

</hibernate-configuration>

I've saved these in a file called faq-service-hiberate.cfg.xml which we can refer to later, of course if you were using proper JPA you would create a persistence.xml etc. In my case I'm using straight Hibernate. Now to the interesting bit. Assuming we have some kind of FAQService defined as an interface with an FAQServiceImpl class providing the implementation to configure and wire these all up in the setUp method of our test using BeanBuilder it is as simple as:

import grails.spring.BeanBuilder
import org.apache.commons.dbcp.BasicDataSource
import org.hibernate.cfg.AnnotationConfiguration
import org.hibernate.SessionFactory
import com.mycompany.FAQService
import com.mycompany.FAQServiceImpl

...

FAQService faqService

protected void setUp() {
def bb = new BeanBuilder()

bb.beans {
dataSource(BasicDataSource) {
url = "jdbc:hsqldb:mem:testDB"
driverClassName = "org.hsqldb.jdbcDriver"
username = "sa"
password = ""
}
sessionFactory(LocalSessionFactoryBean) {
dataSource = dataSource
configLocation = "classpath:com/mycompany/test/faq-service-hiberate.cfg.xml"
configurationClass = AnnotationConfiguration
hibernateProperties = ["hibernate.hbm2ddl.auto":"create-drop"]
}
faqService(FAQServiceImpl) {
sessionFactory = sessionFactory
}
}

def ctx = bb.createApplicationContext()

faqService = ctx.getBean("faqService")
}

So that's pretty neat, whats going on here is I'm dynamically creating 3 beans: the dataSource, sessionFactory and faqService beans. These all get wired together with Spring and I can obtain a reference to the beans using the getBean method of the ApplicationContext.

So how could this be improved? The next step would be to write an abstract base class that constructed the ApplicationContext and then wired the beans into properties of the TestCase, so you could do something like:

 

class FaqServiceTests extends AbstractBeanBuilderDependencyInjectionTests {
def beans = {
// BeanBuilder code here
}

FAQSection faqSection

void testCreateFAQEntry() { }
}

Here it would read the "beans" property construct the ApplicationContext and wire the FAQSection bean into the test before calling the test method. But, I'll save an example of this for a future episode :-)

As for BeanBuilder itself, it is currently shipped as part of Grails, but can be used standalone by getting hold of the grails-spring-1.0.1.jar the latest version of which can be obtained here: http://dist.codehaus.org/grails

Enjoy!

Published at DZone with permission of its author, Graeme Rocher.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Dmitriy Kopylenko replied on Wed, 2008/03/12 - 11:03am

Graeme,

very nice 'how-to'!

One minor typo, though. Instead of showing FaqSection class, you showed FaqElement class twice.

Cheers,

Dmitriy. 

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.