Testing Java Persistence API (JPA) Entities Outside the Container
When I wrote about the Java Persistence API (JPA), Spring and Eclipse Web Tools Platform (WTP) I neglected to show how easy it was to test our JPA entities.
Traditionally, testing EJBs outside of a container was an absolute pain. Testing JPA is embarrassingly easy, especially with Spring doing all the heavy lifting. You’ll have no excuse to write your integration tests now..
Our goal
Our goal here is to test or DAO class, MyDAO, shown here. This can be used as the basis of larger integration tests for example. Here are some of the issues in setting up a test, which we’ll neatly sidestep later:
- We need to set up the entity manager, hopefully just once as this is time consuming.
- We need to handle the automatic dependency injection. Remember that this DAO can run on a server, where it expects the entity manager to be automatically injected.
- We need some kind of transaction management. Ideally we don’t want to start and commit transactions in every test method. DRY!
- A popular way of testing code that interacts with databases these days is to start each test with a transaction, and then after running the test, roll back the transaction so that any database changes aren’t actually committed. This leaves your database in an orderly state.
- Ideally we’d like access to JDBC too - just to check our JPA stuff is doing its thang.
- We may need to handle class instrumentation when using JPA
How to do it
Well, I’m going to show you some code that does all of the above. You’ll be shocked at how little I have to do:
import org.springframework.test.jpa.AbstractJpaTests;
public class MyDAOTests extends AbstractJpaTests {
MyDAO myDAO;
@Override
protected String[] getConfigLocations() {
return new String[] {
"/com/memestorm/service/jpa/applicationContext.xml",
"/com/memestorm/service/jpa/applicationContext-jpa.xml"
};
}
public void testInsertUser() {
Collection users = getMemeService().getUsers();
int found = users.size();
User u = new User("test", "test");
getMemeService().addUser(u);
users = getMemeService().getUsers();
assertEquals(found + 1, users.size());
}
// More tests... then
public MyDAO getMemeService() {
return myDAO;
}
}
I’m sorry, did you blink? Go read that again. Isn’t that neat? Let’s look at what’s happening:
- Obviously the
getConfigLocations()method points to our application context configuration files (I’m using the same ones as in the previous blog entry). Spring uses this to establish the entity manager, database connections etc. It does this once, and all the tests methods use the results. - Spring automatically injected the dependencies. Did you see that
myDAOvariable and its setter? Spring managed that itself. - Spring automatically starts a transaction for every test method and rolls it back afterwards.
- Spring silently handles all class instrumentation.
Now you have no excuses for writing your own tests. You are positively coddled by AbstractJpaTests.
Adding a few features
AbstractJpaTests provides a lot of additional functionality.
JDBC
Sometimes you want to incorporate some JDBC too. Here’s a method that does so:
public void testInsertUserSQL() {
Collection users = getMemeService().getUsers();
int count = jdbcTemplate.queryForInt("SELECT COUNT(*) FROM USER");
assertEquals(users.size(), count);
}
Easy huh? The jdbcTemplate is set up for you in one of the superclasses.
Transactions
You don’t have to wait until the method ends to end the transaction. You can optionally call the method endTransaction() if you like. By default, this will force the rollback of the transaction—remember that’s what typically happens automatically for you at the end of each test method.
If you want the transaction to actually commit for a particular test method, then call the setComplete() method. This sets a flag, read in the endTransaction() method, to tell the test harness to commit instead of rollback. More complex operations can be carried out too, for example you can end a transaction in a test method, and in the same method begin a new one by calling startNewTransaction(). Check out Spring’s AbstractTransactionalSpringContextTests for all of this support.
Entity Manager
You can also get access to the entity manager. Here’s an example that uses some of JPA’s Query interface:
public void testNamedParameter() {
User us = new User("test","last");
getMemeService().storeUser(us);
Query q = this.sharedEntityManager.createQuery(
"select u from User u where u.firstName = :name");
q.setParameter("name", "test");
User u = (User) q.getSingleResult();
assertEquals(u.getFirstName(), "test");
}
As you can see, the exposed sharedEntityManager is the entity manager attached to the current transaction.
Caveats
Say you have a User class that’s in a ManyToOne unidirectional relationship with a Building class, like this:
@Entity
public class User {
/* Unidirectional */
@ManyToOne
@JoinColumn(name="bldng_id")
Building building;
// ..
}
Imagine we write a test case, like this:
public void testBuilding() {
User u = new User("test","test");
Building b = new Building();
b.setName("BuildingA");
u.setBuilding(b);
getMemeService().addUser(u);
**//getMemeService().addBuilding(b);**
}
Here we’ve made a mistake and didn’t persist the building object (it’s required - it’s not cascaded after all). What will happen when you run this test?
Well, the green flag will be raised high, unfortunately. Say we changed the test like so:
public void testBuilding() {
User u = new User("test","test");
Building b = new Building();
b.setName("BuildingA");
u.setBuilding(b);
getMemeService().addUser(u);
Collection usersAfter = getMemeService().getUsers();
}
If you run this test, you’ll get an exception
java.lang.IllegalStateException: During synchronization
a new object was found through a relationship that was
not marked cascade PERSIST.
In other words, the getUsers() method, which simply does em.createQuery("SELECT user from User user").getResultList(), results in the persistence context realizing that there is indeed an object that should have been persisted.
My point is this: I’ve added the final test line there for no good reason, but it’s caught a bug in my persistence logic. That’s a bit ugly - I wish there was some way in which I could force the O/R tool to do some kind of “spot consistency check” to help me out. Hopefully if we have enough tests in our service layer going through to the DAO layer, it’ll catch these types of things. Perhaps Andy over at the excellent Disco Blog will be kind enough to point the way one day ![]()
Print This Post
on November 23rd, 2006 at 8:32 am
afaik and from my experience is User a keyword and doesnt work with mysql or you got some weird results/errors. In our app we changed the name to customer.
just my 2 cents
on January 11th, 2007 at 5:30 pm
I also run into User keyword issue when I tried the web sample on Oracle but figured it out and it worked great. I am not sure if MyDAOTests would run. There is no sample attached and it is not clear to me how myDAO property would be set.
on January 25th, 2007 at 12:43 am
HI, nice article. I’ve written a similar one about DAO testing with Spring and Hibernate, wrapping in a rollback transaction:
http://peterbacklund.blogspot.com/2006/11/testing-hibernate-daos-with-spring.html
Nice to see that explicit JPA support is included in the test hierarchy.
on February 21st, 2007 at 1:58 pm
Simply call the flush method on the sharedEntityManager of the AbstractJpaTests , all inserts and others wainting actions will be done by the EntityManager. You don’t have to call the list to obtain the newly persisted object. Other thing, if you use use find(Class entityClass, Object primaryKey) , you will never flush dbbecause it uses cache, other “find” method with query can’t use cache… so it will cause the entity manager to flush awaiting actions.
on November 8th, 2007 at 10:48 am
I think the reason why that extra at the end of your test looks ugly is because you’re not using the data. What actually bothers me more is not having any assert statements in your test. You could probably solve both problems by adding an assert that uses usersAfter, possibly comparing it to similar data gathered at the beginning of the test.
on July 19th, 2008 at 4:57 am
I have gone through several of your articles and they are really useful. Can you please publish an article as to how to enable logging effectively with spring. There is a lot of confusion about JCL( jakarta commons logging) class loading issues ,JDK 14 logging and log4j. And there is no good article available which addresss this.