We provide a variety of
services including:
·
Training
classes for Spring, Hibernate and
Acegi Security
·
Jumpstarts to get your project off to the right start
·
Reviews to improve your architecture, code and development
process
For more information visit our
website: http://www.chrisrichardson.net
Chris Richardson, chris@chrisrichardson.net
In order to make it easier to write tests for a Hibernate-based persistence tier, I’ve written a simple JUnit extension called ORMUnit. It provides the following subclasses of JUnit TestCase:
· HibernateORMappingTests: For testing a Hibernate object/relational mapping
· HibernatePersistenceTests: For testing Hibernate objects and queries
· HibernateSchemaTests: For testing the database schema
HibernateORMappingTests simplifies the task of testing the object/relational mapping. It provides methods for making assertions about the mapping. For example, they make it easy to write a test that verifies that all of a class’s fields or properties are mapped to the database.
HibernatePersistenceTests makes it easier to write tests for persistent objects and queries. It takes care of initializing the database; provides access to HibernateTemplate; and manages transactions.
HibernateSchemaTests
makes it easy to verify that the database schema matches the O/R mapping.
There are two ways to include the ORMUnit jar in your application. If you are using Maven you can get it from the following repository:
<repositories>
<repository>
<id>pia-repository</id>
<url>http://www.pojosinaction.com/repository</url>
</repository>
<repositories>
<dependency>
<groupId>net.chrisrichardson</groupId>
<artifactId>ormunit-hibernate</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
Alternatively, you can get the source code from http://code.google.com/p/ormunit/
TODO – one day there will be a downloadable zip
The ORMUnit test classes are intended to be used in a Spring application (however, see below on how you can use other ORMUnit classes without Spring). They extend AbstractDependencyInjectionSpringContextTests. They define various public setters including setSessionFactory().
Let’s first look at how to test the O/R mapping. This may seem strange at first but testing the O/R mapping is a quick and easy way to detect some kinds of bugs. For example, one common mistake is forgetting to write the O/R mapping for a newly added field or property. You could detect this bug by writing a test that saves and object and verifies that corresponding column contains the expected value. However, that can be a lot of work and the tests might take a while to execute. In comparison, testing the mapping is a lot easier and the tests are fast.
Here is an example of such as test:
public class ExampleMappingTests
extends HibernateORMappingTests {
@Override
protected String[] getConfigLocations() {
return new String[] {"classpath:appctx/context.xml" };
}
public void testAllClassesMapped() {
assertAllClassesMapped();
}
}
This test will throw an exception if any persistent class contains an unmapped.
By default, ORMUnit checks for unmapped properties but you can check for unmapped fields by calling setAccessStrategy():
public class ExampleMappingTests
extends HibernateORMappingTests {
protected void onSetUp() throws Exception {
super.onSetUp();
mappingChecker.setAccessStrategy(AccessStrategy.FIELD);
}
public void testAllClassesMapped() {
assertAllClassesMapped();
}
}
ORMUnit can also verify that the database schema matches the O/R Mapping. It’s not uncommon for the two to quietly diverge. Hibernate can be configured to check but if you are not using that option then this can be a quick and easy way to check.
Here is an example of a test:
public class PointlessSchemaTest extends HibernateSchemaTests {
@Override
protected String[] getConfigLocations() {
return new String[] { "classpath:appctx/context.xml" };
}
public void testSchema() throws Exception {
assertDatabaseSchema();
}
}
The test will fail if any tables or columns are missing from the database schema.
ORMUnit defines the class HibernatePersistenceTests, which makes it easier to write tests that CRUD persistent objects.
Here is an example of a really simple CRUD test that saves a persistent object and then loads it.
public class CustomerPersistenceTests extends HibernatePersistenceTests<CustomerPersistenceTests> {
@Override
protected String[] getConfigLocations() {
return new String[] { "classpath:appctx/context.xml" };
}
public void testSimple() {
Customer c = new Customer("John Doe", new Address("1
High Street", null, "
"94610"));
int id = (Integer) save(c);
Customer c2 = get(Customer.class, id);
assertNotNull(c2);
}
}
There are a few notable things. The test calls save() to persist the object and get() to load it. These methods are provided by HibernatePersistenceTests. Note that you don’t need to cast the return value of get().
By default, HibernatePersistenceTests executes each test in a transaction which is rolled back at the end. This improves performance and ensures that the database is unchanged, ready for the next test. However, because objects are cached in the Hibernate Session and changes are queued until flush-time the test might not do exactly what you think it does. In the above example, the get() does not actually execute a SQL SELECT because the customer is already in the Session.
HibernatePersistenceTests provides a couple of ways to address this problem:
· Automatic flushing and clearing of the Session
· Committing transactions but resetting the database at the start of a test
One approach is to flush() and clear() the session between each step of the test. Here is one way to write a test that does this.
public class CustomerPersistenceTests extends
HibernatePersistenceTests<CustomerPersistenceTests> {
public void testUpdateSimpleV1() {
doWithTransaction(new TxnCallback(){
public void execute() throws Throwable {
Customer c = new Customer("John Doe",
new
Address("
null, "
id = save(c);
}});
doWithTransaction(new TxnCallback(){
public void execute() throws Throwable {
Customer c2 = get(Customer.class, id);
assertNotNull(c2);
}});
}
This test uses doWithTransaction() to demarcate each step of the test. It flushes and clears the Session.
One drawback of doWithTransaction() is that it’s a little ugly. Here is a simpler (yet more magical way) to accomplish the same thing.
public class CustomerPersistenceTests extends
HibernatePersistenceTests<CustomerPersistenceTests> {
public void testUpdateSimpleV2() {
Serializable id = txnThis.saveCustomer();
txnThis.loadCustomer(id);
}
Serializable saveCustomer() {
Customer c = new Customer("John Doe", new Address("1
High Street",
null, "
return save(c);
}
void loadCustomer(Serializable id) {
Customer c2 = get(Customer.class, id);
assertNotNull(c2);
}
In this example, txnThis (a protected variable defined by HibernatePersistenceTests) is a proxy that executes the method within a doWithTransaction().
An alternative approach, which avoids the problems caused by a long-lived Hibernate sessions, is to commit the transaction and not use a single transaction (and session) for the duration of the test. The ORMUnit transaction handling mechanism is pluggable and is configured by a HibernatePersistenceTestsStrategy that’s injected into HibernatePersistenceTests. The default implementation is RollbackTransactionHibernatePersistenceTestsStrategy, which implements the strategy described above of rolling back the transaction. The other implementation is SimpleHibernatePersistenceTestsStrategy, which commits transactions.
When using this strategy the tests are not wrapped in a transaction. The doWithTransaction() method executes the callback in a real transaction. Moreover, Hibernate Sessions are only opened when necessary: by calls to HibernateTemplate and doWithTransaction(). This means that objects are cached for shorter periods of time and the session is flushed more regularly.
One drawback of committing transactions is that tests can change the database. To solve this problem SimpleHibernatePersistenceTestsStrategy resets the database at the start of each test by using a DatabaseResetStrategy. There is an implementation of this class that recreates the schema using Hibernate hbm2dll.
You would, for example, configure the HibernatePersistenceTestsStrategy and DatabaseResetStrategy by including the following bean definition in the application context:
<bean
id="hibernatePersistenceTestsStrategy"
class="net.chris…SimpleHibernatePersistenceTestsStrategy">
<constructor-arg>
<bean
class="net.chris…ResetDatabaseByRecreatingSchemaStrategy"
/>
</constructor-arg>
<constructor-arg>
<bean
class="org.springframework.transaction.support.TransactionTemplate"
>
<property
name="transactionManager"
ref="transactionManager"
/>
</bean>
</constructor-arg>
</bean>
Note regardless of the strategy your tests would still use doWithTransaction() or txnThis to demarcate steps of the test.
ORMUnit was intended to be used in a Spring application. However, it’s quite easy to use some parts of ORMUnit without Spring since the Spring-specific test classes are simply wrappers around other classes:
Spring-specific class |
Non-Spring classes |
HibernateMappingTests HibernateORMappingTests |
HibernateMappingChecker MappedClassChecker |
HibernateSchemaTests |
HibernateSchemaChecker |
HibernatePersistenceTests |
HibernatePersistenceTestsStrategy and DatabaseResetStrategy |
The ORMUnit tests