Testing Databases with JUnit and Hibernate Part 1: One to Rule them
Databases are an extremely important part of almost every enterprise application. Yet there is very little support for testing your database, which results in very little tests coverage of database related code out in the wild. In a desperate attempt to change that at least a little the article series starting with this article will describe some of the problems and possible partial solutions based on Hibernate and JUnit.
As an example I use the following little set of Hibernate entity classes and the database schema Hibernate will create from it:
-
@Entity
-
public class SuperHero extends AbstractEntity {
-
-
@NotEmpty
-
@Column(unique = true)
-
public String name;
-
-
@ManyToOne
-
@NotNull
-
public SuperPower power;
-
-
@NotEmpty
-
public String weakness;
-
-
@NotEmpty
-
public String secretIdentity;
-
}
-
@Entity
-
public class SuperPower extends AbstractEntity {
-
-
@NotEmpty
-
@Column(unique = true)
-
public String name;
-
-
@NotEmpty
-
public String description;
-
-
@NotNull
-
@ManyToOne
-
public SuperPowerType type;
-
}
-
@Entity
-
public class SuperPowerType extends AbstractEntity {
-
-
@NotEmpty
-
@Column(unique = true)
-
public String name;
-
-
@NotNull
-
@Length(min = 30)
-
public String description;
-
}
As you can see (and if you don’t I tell you) a SuperHero references a SuperPower and a SuperPower references a SuperPowerType. All have some mandatory attributes.
All three entities have a common supertype providing id and version.
-
@MappedSuperclass
-
public class AbstractEntity {
-
-
@SuppressWarnings("unused")
-
@Id
-
@GeneratedValue
-
public Long id;
-
@SuppressWarnings("unused")
-
@Version
-
private Long version;
-
}
For brevity I skipped on getters and setters.
Finally there is a SuperHeroRepository which I want to test. It has a single method returning a SuperHero based on the a SuperPower
A simple test for the SuperHeroRepository might work like this:
Create a SuperHero and make sure I can retrieve it again using the SuperHeroRepository.
Easy, isn’t it? Well … lets see what you really have to do
- create an empty database
- create a SuperPowerType, making sure you have all the mandatory fields filled
- create a SuperPower of that type, making sure you have all the mandatory fields filled
- create a SuperHero, making sure you have all the mandatory fields filled
- use the repository to retrieve SuperHeros
- assert you got the expected result.
So we have to fill all the mandatory fields and provide a SuperPowerType (again with all the mandatory fields) although nothing in the test is concerned with SuperPowerTypes. If you still don’t believe this results in ugly code have a look at this naive implementation.
-
public class SuperHeroRepository1Test {
-
private SessionFactory sessionFactory;
-
private Session session = null;
-
-
@Before
-
public void before() {
-
// setup the session factory
-
AnnotationConfiguration configuration = new AnnotationConfiguration();
-
configuration.addAnnotatedClass(SuperHero.class)
-
.addAnnotatedClass(SuperPower.class)
-
.addAnnotatedClass(SuperPowerType.class);
-
configuration.setProperty("hibernate.dialect",
-
"org.hibernate.dialect.H2Dialect");
-
configuration.setProperty("hibernate.connection.driver_class",
-
"org.h2.Driver");
-
configuration.setProperty("hibernate.connection.url", "jdbc:h2:mem");
-
configuration.setProperty("hibernate.hbm2ddl.auto", "create");
-
-
sessionFactory = configuration.buildSessionFactory();
-
session = sessionFactory.openSession();
-
}
-
-
@Test
-
public void returnsHerosWithMatchingType() {
-
-
// create the objects needed for testing
-
SuperPowerType powerType = new SuperPowerType();
-
powerType.name = "TheType";
-
powerType.description = "12345678901234567890aDescription";
-
-
SuperPower superpower = new SuperPower();
-
superpower.name = "SuperPower";
-
superpower.description = "Description";
-
superpower.type = powerType;
-
-
SuperHero hero = new SuperHero();
-
hero.name = "Name";
-
hero.power = superpower;
-
hero.weakness = "None";
-
hero.secretIdentity = "Mr. Jones";
-
-
// storing the objects for the test in the database
-
session.save(powerType);
-
session.save(superpower);
-
session.save(hero);
-
-
SuperHeroRepository heroRepository = new SuperHeroRepository(session);
-
List<SuperHero> heroes = heroRepository.loadBy(superpower);
-
assertNotNull(heroes);
-
assertEquals(1, heroes.size());
-
}
-
-
@After
-
public void after() {
-
session.close();
-
sessionFactory.close();
-
}
-
}
There is really only one positive thing I can say about this test: it uses H2 in In-Memory mode so it is reasonable fast. There is the first lesson: *Use an in memory database for testing if possible. It’s easily 100*faster then a disc based database*
So obviously we want to refactor this monster, which might after some method extracting result into something like this:
-
public class SuperHeroRepository2Test {
-
-
private SessionFactory sessionFactory;
-
private Session session;
-
-
@Before
-
public void before() {
-
sessionFactory = createSessionFactory();
-
session = sessionFactory.openSession();
-
Transaction transaction = session.beginTransaction();
-
}
-
-
@Test
-
public void returnsHerosWithMatchingType() {
-
-
SuperPowerType powerType = createSuperPowerType();
-
session.save(powerType);
-
-
SuperPower superpower = createSuperPower(powerType);
-
session.save(superpower);
-
-
SuperHero hero = createSuperHero(superpower);
-
session.save(hero);
-
-
SuperHeroRepository heroRepository = new SuperHeroRepository(session);
-
List<SuperHero> heroes = heroRepository.loadBy(superpower);
-
-
assertNotNull(heroes);
-
assertEquals(1, heroes.size());
-
}
-
-
private SessionFactory createSessionFactory() {
-
AnnotationConfiguration configuration = new AnnotationConfiguration();
-
configuration.addAnnotatedClass(SuperHero.class)
-
.addAnnotatedClass(SuperPower.class)
-
.addAnnotatedClass(SuperPowerType.class);
-
configuration.setProperty("hibernate.dialect",
-
"org.hibernate.dialect.H2Dialect");
-
configuration.setProperty("hibernate.connection.driver_class",
-
"org.h2.Driver");
-
configuration.setProperty("hibernate.connection.url", "jdbc:h2:mem");
-
configuration.setProperty("hibernate.hbm2ddl.auto", "create");
-
-
SessionFactory sessionFactory = configuration.buildSessionFactory();
-
return sessionFactory;
-
}
-
-
private SuperHero createSuperHero(SuperPower superpower) {
-
SuperHero hero = new SuperHero();
-
hero.name = "Name";
-
hero.power = superpower;
-
hero.weakness = "None";
-
hero.secretIdentity = "Mr. Jones";
-
return hero;
-
}
-
-
private SuperPower createSuperPower(SuperPowerType powerType) {
-
SuperPower superpower = new SuperPower();
-
superpower.name = "SuperPower";
-
superpower.description = "Description";
-
superpower.type = powerType;
-
return superpower;
-
}
-
-
private SuperPowerType createSuperPowerType() {
-
SuperPowerType powerType = new SuperPowerType();
-
powerType.name = "TheType";
-
powerType.description = "12345678901234567890aDescription";
-
return powerType;
-
}
-
}
This would be ok, if this would be the only test of this kind. But other similar test need the SessionFactory as well so I will move all the SessionFactory, Session and Transaction stuff into a Rule
-
public class SessionFactoryRule implements MethodRule {
-
private SessionFactory sessionFactory;
-
private Transaction transaction;
-
private Session session;
-
-
@Override
-
public Statement apply(final Statement statement, FrameworkMethod method,
-
Object test) {
-
return new Statement() {
-
-
@Override
-
public void evaluate() throws Throwable {
-
sessionFactory = createSessionFactory();
-
createSession();
-
beginTransaction();
-
try {
-
statement.evaluate();
-
} finally {
-
shutdown();
-
}
-
}
-
-
};
-
}
-
-
private void shutdown() {
-
try {
-
try {
-
try {
-
transaction.rollback();
-
} catch (Exception ex) {
-
ex.printStackTrace();
-
}
-
session.close();
-
} catch (Exception ex) {
-
ex.printStackTrace();
-
}
-
sessionFactory.close();
-
} catch (Exception ex) {
-
ex.printStackTrace();
-
}
-
}
-
-
private SessionFactory createSessionFactory() {
-
AnnotationConfiguration configuration = new AnnotationConfiguration();
-
configuration.addAnnotatedClass(SuperHero.class)
-
.addAnnotatedClass(SuperPower.class)
-
.addAnnotatedClass(SuperPowerType.class);
-
configuration.setProperty("hibernate.dialect",
-
"org.hibernate.dialect.H2Dialect");
-
configuration.setProperty("hibernate.connection.driver_class",
-
"org.h2.Driver");
-
configuration.setProperty("hibernate.connection.url", "jdbc:h2:mem");
-
configuration.setProperty("hibernate.hbm2ddl.auto", "create");
-
-
SessionFactory sessionFactory = configuration.buildSessionFactory();
-
return sessionFactory;
-
}
-
-
public Session createSession() {
-
session = sessionFactory.openSession();
-
return session;
-
}
-
-
public void commit() {
-
transaction.commit();
-
}
-
-
public void beginTransaction() {
-
transaction = session.beginTransaction();
-
}
-
-
public Session getSession() {
-
return session;
-
}
-
}
Which leaves the test in a somewhat acceptable state.
-
public class SuperHeroRepository3Test {
-
-
@Rule
-
public final SessionFactoryRule sf = new SessionFactoryRule();
-
-
@Test
-
public void returnsHerosWithMatchingType() {
-
Session session = sf.getSession();
-
-
SuperPowerType powerType = createSuperPowerType();
-
session.save(powerType);
-
-
SuperPower superpower = createSuperPower(powerType);
-
session.save(superpower);
-
-
SuperHero hero = createSuperHero(superpower);
-
session.save(hero);
-
-
sf.commit();
-
-
SuperHeroRepository heroRepository = new SuperHeroRepository(session);
-
List<SuperHero> heroes = heroRepository.loadBy(superpower);
-
-
assertNotNull(heroes);
-
assertEquals(1, heroes.size());
-
}
-
-
// … private methods more or less as before
-
}
All the SessionFactory creation is now moved out into a Rule and is therefore perfectly reusable. You now can change the way you create your SessionFactory at a single point for all tests in need of a database. This is nice and it shall be enough for today. Next week we’ll take care of our SuperHero and her dependencies.






[...] This blog post discusses the problem of testing the database code. It describes some of the problems and proposes possible partial solutions to this testing issue based on Hibernate and JUnit. [...]
THANK YOU for sharing!
although i get an exception when closing the session?
org.hibernate.TransactionException: Transaction not successfully started
at org.hibernate.transaction.JDBCTransaction.rollback(JDBCTransaction.java:149)
at de.schauderhaft.rules.SessionFactoryRule.shutdown(SessionFactoryRule.java:43)
Hi knrr,
I’m sorry about the exception but can’t really tell what the problem is. I have all the different pieces and versions in a single project. It might be that I mixed up some not matching version.
I’ll make all the source code available as one download at the end of the series.
Thx for shouting THANK YOU. That’s really nice.
Jens
Nice post, and nice exposition of problem, and a possible solution if you are using Hibernate. In fact if you are using Spring Framework, JUnit testing is much easier.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={“/META-INF/spring/applicationContext.xml”})
@TransactionConfiguration(transactionManager=”transactionManager”, defaultRollback=false)
@Transactional
public class OneToManyPerformanceTest {
@Test
public void returnsHerosWithMatchingType() {
}
}
An interesting problem and an interesting solution with the @Rule.
What if you need some data to be already in the DB? I suppose that http://www.DbUnit.org would be the most suitable tool for that?
You haven’t mentioned what you use to create the database and its tables. How do you do it?
I tend to use dbunit-embeddedderby-parenttest (DbUnit Test Skeleton), an extension of DbUnit, to easily create a DB from a ddl, initialize it with data from a DBUnit XML file, and test more easily its content. It uses Derby by default (sometimes it’s really useful to be able to look at the data after tests finish) but could use H2 equally well. End of self-promotion
Good luck with your testing (ad)ventures!
With these kind of tests the tables get created by Hibernate for each test a new. t’s actually in the code above:
configuration.setProperty(“hibernate.hbm2ddl.auto”, “create”);
The data gets created inside the test using Java/Hibernate. I
I do this because it is friendly to refactoring and makes the tests as small as possible.
With such small tests I found the sql logging of hibernate to be sufficient for debugging.
Of course there is also a place for tests based larger datasets. For these I would consider DbUnit as well.
Shameless plug: We used the same strategy but found that the time the unit tests took on a growing JPA project was increasing exponential because of the dropping/creating of the schema for each test. To solve this we created a small library that will re-use the same schema for all tests: http://markatta.com/jee5unit/index.html
@Johan plugs like this are highly welcome.
Runtime of the tests is an issue. I used multiple approaches to handle this:
- move this kind of tests into a seperate build step
- let it run against an in memory database
- those tests that need something ugly like oracle which doesn’t come in an in-memory version we succeeded in stripping it down to the essentials and run it on a ram-disc, this improved the performance a lot. (although they are still slow as mud compared to proper unit tests.
As Alex said – testing database related stuff with spring is easy and fast.
In my JEE projects I create schema once before all tests (using hibernate) and flush hibernate session and rollback transaction on every test end.
I don’t use deferred constraints, so this strategy isn’t more error-prone than commiting transactions, and this way even hundreds of database tests take less than 30 seconds to execute.
If You use the same hibernate session to create test data and execute some service method then watch out for side effects, because some data is already in cache and won’t be requested from the database the tested code.
[...] logic, which depends on data and database features, is often difficult to test. Partly, these difficulties arise from the heavy-weight nature of many databases (see Problem 1) [...]
Hi Jens,
Thanks for the article. I was wondering how would you suggest best using this method if my project is not using annotated hibernate classes but rather hbm.xml files. Thanks again!!
@Eric You can do the same thing with xml mapped Hibernate Entities. You just need a different Configuration. I think this on is the one you need: http://docs.jboss.org/hibernate/core/3.5/api/org/hibernate/cfg/Configuration.html
In general use a setup as similar as possible as your production code, so if you use Spring you probably best served using the Spring-JUnit-Testing-Support classes.
Hi Jens-
I’m not using Spring, but I am using Struts… do ou think using your approach would be ok with that framework?
Sure, Struts is a webframework. It shouldn’t affect how you test your database.
Hi Jens! I am running into the following error when I try to create a new SessionFactoryRule..
org.hibernate.validator.event.ValidateEventListener cannot be cast to org.hibernate.event.PreInsertEventListener
I have commented out everything in my test class except for
@Rule
public final SessionFactoryRule sf = new SessionFactoryRule();
Any ideas what is going on?
Addendum: I found this posting online, https://forum.hibernate.org/viewtopic.php?f=1&t=972729, which suggested making the call to get the session static. I did this, which got rid of the validating casting errors.
I am now getting an exception initialization error at this line:
@Rule
public final SessionFactoryRule sf = new SessionFactoryRule();
I think this might have something to do with trying to instantiate a new factory rule when I use a static method to get the session. Is there anyway to avoid this error while still using the static method to avoid the casting errors? My code looks like this
private static final SessionFactory hibernateSessions;
static
{
log.info(“Session Factory Created”);
try {
Class.forName(“org.h2.Driver”);
} catch (ClassNotFoundException e1) {
e1.printStackTrace();
}
try {
DriverManager.getConnection(“jdbc:h2:mem:”, “sa”, “”);
} catch (SQLException e) {
e.printStackTrace();
}
AnnotationConfiguration configuration = new AnnotationConfiguration();
//configuration.addAnnotatedClass(Facility.class);
configuration.setProperty(“hibernate.dialect”, “org.hibernate.dialect.H2Dialect”);
configuration.setProperty(“hibernate.connection.driver_class”, “org.h2.Driver”);
configuration.setProperty(“hibernate.connection.url”, “jdbc:h2:mem:”);
configuration.setProperty(“hibernate.hbm2ddl.auto”, “create”);
hibernateSessions = configuration.buildSessionFactory();
// return sessionFactory;
}
public Session createSession() {
// session = sessionFactory.openSession();
// return session;
return hibernateSessions.openSession();
}
Never mind, got it working, thanks!