Coming Up for Air

Unit Testing EJBs

As we’ve done more and more EJB development, we’ve had to think pretty hard about how to unit test our beans. We’ve tried a couple of different approaches (including not testing, which I don’t recommend ;), but weren’t ever just real comfortable with the results. I’m pretty happy with the method we’re using now, and it’s so simple, I’m a bit embarrassed that we didn’t think of it earlier.

One of our earliest attempts used JBoss' embeddable container, which we eventually discarded for a couple of reasons. It was really heavy in terms of configuration and startup time, and, as hard as I’m sure the JBoss team is working on AS 5 in terms of EE 5 compliance, the embeddable container is still using an old version of the spec, which meant we had to change the way we handled resource look ups, which we didn’t like. We’ve also tried using mock objects, but, for the most part, we found that to be overkill. What we ended up with is pretty light and simple.

To make things work, we had to change the way we setup our EJBs. Since the beginning our EE 5 development, we’ve been using field injection:

1
2
@EJB(mappedName="ejb/MyEjb")
private MyEjbInterface myEjb;

which we have changed to using property injection:

1
2
3
4
5
private MyEjbInterface myEjb;
@EJB(mappedName="ejb/MyEjb")
public void setMyEjb(MyEjb myEjb) {
    this.myEjb = myEjb;
}

Our general approach has been to avoid making changes to production code for the purpose of testing, but, after talking things over with a friend from our local JUG, I felt the change was not only appropriate, but a good general philosophy change. What Rod explained to me is that, by using property injection, we make our beans reusable across integration technologies. If, for example, we ever wanted to use this class with Guice or Spring, we’d be out of luck using field injection, as they don’t support that. They do, though, support property injection, so using that makes sense in terms of portability.

With that change made, our unit tests became a lot easier to write. Our applications are all multi-tiered, with JSF handling the view, and service and DAO layers that are either Faces-managed or container-managed and Session Beans, depending on the need for remote access. For the purposes of our discussion here, we’ll start with the data layer. Let’s say we have this simple DAO:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Stateful
public class UserDaoImpl implements UserDao {
    private EntityManager entityManager;
    @PersistenceContext(unitName="myapp")
    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }
    public User addUser(String userName, String emailAddress) {
        User user = new User(userName, emailAddress);
        entityManager.persist(user);
        return user;
    }
}

This is a very simple DAO, and there is a lot intentionally left out, but it demonstrates a simple DAO using JPA. So how do we test that? How do we create the EntityManager, for example? Here’s how the unit test might look using JUnit 4 and DBUnit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class UserDaoImplTest {
    private static UserDaoImpl dao = new UserDaoImpl();
    private static EntityManagerFactory emf = Persistence.createEntityManagerFactory("myapp-test");
    private static EntityManager entityManager;
    @BeforeClass
    public static void classSetup() {
        try {
            entityManager = (EntityManager) emf.createEntityManager();
            DbUnitHelper.initDatabase(entityManager, "doc/sql/myapp.sql");
            dao.setEntityManager(entityManager);
        } catch (Exception e) {
            throw new RuntimeException (e);
        }
    }
    @AfterClass
    public static void classTearDown() {
        try {
            DbUnitHelper.closeConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    @Before
    public void setUp() throws DatabaseUnitException, SQLException, Exception {
        DbUnitHelper.loadData("/dataset.xml");
    }
    @Test
    public void addUser() {
        DbUnitHelper.startTransaction(entityManager);
        User user = dao.addUser("Jim Halpert", "[email protected]");
        Assert.assertNotNull(user);
        DbUnitHelper.rollbackTransaction(entityManager);
    }
}

The unit test starts by declaring some static resources: the DAO implementation, an EntityManagerFactory, and an EntityManager. In the @BeforeClass, we ask the EMF for the EntityManager, then pass that to a DBUnit helper class, then set the EM on the DAO (you should see now where the property injection comes in handy). The @Before method simply loads our test data before each test. One could just as easily have each unit test rollback at the end, but we chose to reload our small test dataset before each run. The unit test itself is fairly straightforward, except that we are explicitly handling transactions to mimic the container-managed transaction handling we get for free in production.

The persistence context is defined like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
    version="1.0">
    <persistence-unit name="myapp-test" transaction-type="RESOURCE_LOCAL">
        <class>com.steeplesoft.myapp.model.beans.User</class>
        <exclude-unlisted-classes/>
        <properties>
            <property name="toplink.jdbc.driver" value="org.postgresql.Driver"/>
            <property name="toplink.jdbc.url" value="jdbc:postgresql://localhost/myapp"/>
            <property name="toplink.jdbc.user" value="myapp"/>
            <property name="toplink.jdbc.password" value="myapp"/>
            <property name="toplink.logging.level" value="FINE"/>
        </properties>
    </persistence-unit>
</persistence>

Here is the code for the DBUnitHelper class. Note that this is still experimental, but I’ve used it on three projects now with good success, but it does abuse the JPA API by talking to the TopLink objects directly, so it is definitely not portable. You have been warned. :)

Testing our service is fairly similar, needing just a bit more setup:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class MyAppServiceImplTest {
    private static MyAppServiceImpl service = new MyAppServiceImpl();
    private static UserDaoImpl dao = new UserDaoImpl();
    private static EntityManagerFactory emf = Persistence.createEntityManagerFactory("myapp-test");
    private static EntityManager entityManager;
    @BeforeClass
    public static void classSetup() {
        try {
            entityManager = (EntityManager) emf.createEntityManager();
            DbUnitHelper.initDatabase(entityManager, "doc/sql/myapp.sql");
            dao.setEntityManager(entityManager);
            service.setUserDao(dao);
        } catch (Exception e) {
            throw new RuntimeException (e);
        }
    }
    @AfterClass
    public static void classTearDown() {
        try {
            DbUnitHelper.closeConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    @Before
    public void setUp() throws DatabaseUnitException, SQLException, Exception {
        DbUnitHelper.loadData("/dataset.xml");
    }
    @Test
    public void addUser() {
        DbUnitHelper.startTransaction(entityManager);
        User user = service.addUser("Jim Halpert", "[email protected]");
        Assert.assertNotNull(user);
        DbUnitHelper.rollbackTransaction(entityManager);
    }
}

This test is very similar to the DAO test, including transaction management, but we call the service instead of the DAO. I also chose to create a "real" instance of the DAO as opposed to mocking one up, since the resource was local to the project. That makes the test more of an integration test in some ways, but we’re OK with that.

For external resources, we’re tempted to use mock objects, but another sharp OKC JUG regular, Dave Nicolette, suggests that that might be overkill. Anything we might inject we will have an interface for, so he suggests just writing a stubbed implementation of the interface and injecting that, making our "stub" behave the way our test expects, which would allow us to focus on testing the client and not the "remote" object. That’s an interesting approach. I have not been able to test that yet, but I certainly will when the need arises.

That about sums it up. All that’s left to test is the JSF layer, for which we don’t have a solution with which I’m all that comfortable. Once we nail something down, I’ll be sure to write it up here. :)

So, how is everyone else testing EJBs? Comments, suggestions, corrections, etc. are, of course, welcome!

tags: EJB3 Java testing

Quotes

Sample quote

Quote source