MemeStorm

Exploring the Spring Framework and Application Development

Testing Java Persistence API (JPA) Entities Outside the Container

Posted in All by Jon on the November 6th, 2006

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 myDAO variable 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 Print This Post