Sunday, March 04, 2007

Testing EJB3 (JPA) with Maven2

This tutorial describes how to test EJB3 beans as a part of a Maven2 build, i.e. without an EJB container but with an in-memory, transactional database. I make the following assumptions:
  • JDK 1.5
  • EJB 3.0, i.e. Java Persistence API (JPA) 1.0
  • Maven 2 (2.0.5)
I don't assume much more than that. Our application contains no explicit references to Spring or Hibernate as EJB3 itself borrows much of their goodness. We avoid all the XML config usually associated with Spring/Hibernate by using the JPA persistence annotations in our entity beans. To facilitate testing, any injected dependency references, e.g. @PersistenceContext or @EJB, are declared package access so that the JUnit test classes can set them appropriately.

As with many enterprise applications, much of our business logic is embedded within database queries, most in JPQL, and some can be quite complex. It's vital that we're able to test them outside of a container as a part of our normal, continuously-integrated build. For that, we use the Hypersonic (hsqldb) in-memory database. We'll need a JPA implementation, of course, and because we deploy to JBoss 4.0 in production, which uses Hibernate's JPA, we choose the same. Of course, any certified JPA implementation should work just as well.

pom.xml

So our first step is to declare our testing dependencies in our POM:

<!-- For in-memory sql testing -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>3.2.0.ga</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>1.8.0.7</version>
<scope>test</scope>
</dependency>

Aside: I toyed with using the new H2 database instead of Hypersonic, but the version I tested, 1.0.20061217, included only table locks, causing my app to deadlock. Hypersonic supports row locking.

Although not strictly necessary, I recommend adding this to your POM, too:

<testResources>
<testResource>
<directory>src/test/resources</directory>
<filtering>true</filtering>
</testResource>
</testResources>

You'll see why in the next section.

Note that we didn't need to change the POM much. In particular, no special configuration of the Surefire plugin (the testcase invoker) is required.

src/test/resources/META-INF/persistence.xml

All JPA apps need a persistence.xml file. Your app will likely already have one beneath src/main/resources. Both will be in your CLASSPATH when the tests are run. The JPA will aggregate them, so you need to make sure all your persistence-units, both real and test, are uniquely named. How you organize your persistence-units is up to you. It's probably easiest to have just one for testing, or you might, for example, prefer to create one per test class.

Here's what I recommend:

<persistence>
<persistence-unit name="hibernate-hsqldb">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jar-file>${project.build.directory}/classes</jar-file>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
<property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver"/>
<property name="hibernate.connection.url" value="jdbc:hsqldb:."/>
<property name="hibernate.connection.username" value="sa"/>
<property name="hibernate.connection.password" value=""/>
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
</properties>
</persistence-unit>
</persistence>

Note the jar-file element. The ${...} expansion only works when filtering is turned on in the testResources element of the POM (see above). By setting the jar-file this way, we're telling the persistence provider to find our entities by searching for persistence annotations in our classes. From these, the required DDL is generated and the schema is created. Alternatively, you could use one or more class elements instead. This would allow you finer-grained control over which classes and/or packages are involved in a test. This strategy would probably also lead to multiple persistence-unit elements defined.

One thing to keep in mind about Hibernate. There's a known bug in which it won't create a schema before creating tables for your entities, so you're in trouble if you've set the schema attribute in your @Table annotations. I recommend removing this attribute from your entities anyway: it's a violation of the DRY principle. A better solution is to set the hibernate.default_schema property in your persistence-unit.

One debugging tip: you may want to include the following property in your persistence-unit:

<property name="hibernate.show_sql" value="${hibernate.show_sql}"/>

That way, you could add something along these lines in your POM:

<properties>
<hibernate.show_sql>false</hibernate.show_sql>
</properties>
<profiles>
<profile>
<id>debug</id>
<properties>
<hibernate.show_sql>true</hibernate.show_sql>
</properties>
</profile>
</profiles>

So when you want to see the SQL produced by your unit tests...

$ mvn -Pdebug test

src/test/resources/import.sql

Some apps make use of Hibernate's ability to run some SQL after a schema is created. If it finds a file named import.sql on the classpath, it'll run the SQL within it. This can be a problem if, for example, your import.sql is exploiting Oracle PL/SQL commands that wouldn't make any sense to Hypersonic. Fortunately, you can easily solve the problem by simply putting a different -- possibly empty -- import.sql file beneath src/test/resources, because Hibernate will find that one first in the CLASSPATH.

Aside: I experienced some problems with a test-specific import.sql file, strange stack traces generated by Hibernate's SchemaExport class dumped to stdout without actually failing the tests. I ended up truncating src/test/resources/import.sql completely.

src/test/java/org/yours/SomeTest.java

Speaking of unit tests, here's an obviously-contrived example:

package org.yours;

import javax.persistence.*;
import junit.framework.*;
import org.apache.log4j.Logger;

public class SomeTest extends TestCase
{
public void testService() throws Exception
{
log.info ("testService");
EntityManager em = emf.createEntityManager();
TestData.build (em);
ServiceBean slsb = new ServiceBean();
slsb.em = em;
assertTrue (slsb.something());
}
protected void setUp() throws Exception
{
log.debug ("setUp");
emf = Persistence.createEntityManagerFactory ("hibernate-hsqldb");
}
protected void tearDown() throws Exception
{
log.debug ("tearDown");
emf.close();
}
private Logger log = Logger.getLogger(getClass());
private EntityManagerFactory emf;
}

There is a lot of room for artistic freedom within this structure, but the essential point is the create/close of the EntityManagerFactory in the setUp/tearDown methods. This provides a clean database to each testXXX method.

If your transactional requirements are minimal, you may also want to create/close an EntityManager member variable in setUp/tearDown, too.

If your seed data requirements are complex, you may want to look into something like DBUnit, but in my experience, it's often easier to construct "builder" objects that can model various situations for your tests.

Transactions

For a lot of test cases, you can probably safely ignore transactions. Within one test, letting each of your beans use the same EntityManager without ever beginning or committing its transaction may go a long way toward testing your app sufficiently, especially if all of your beans are simply propagating the transaction anyway.

But transactions can get icky quickly, especially when you have multiple cooperating session beans with their transaction attribute set to REQUIRES_NEW. I'm going to show you one way of solving the problem using Java's dynamic proxies, but there's probably a more elegant solution.

Here's something I call a TransactionProxy. Others might call it "a good argument for AOP". It assumes your target bean class has an EntityManager member named em.

public class TransactionProxy implements InvocationHandler
{
private EntityManagerFactory emf;
private Object target;
private Field field;

private TransactionProxy (Object target, EntityManagerFactory emf)
{
this.emf = emf;
this.target = target;
try {
this.field = target.getClass().getDeclaredField ("em");
this.field.setAccessible (true);
} catch (Exception e) {
throw new RuntimeException (e);
}
}

public static Object newInstance (Object target, EntityManagerFactory emf)
{
return Proxy.newProxyInstance (target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new TransactionProxy (target, emf));
}

public Object invoke (Object proxy, Method m, Object[] args)
throws Throwable
{
EntityManager em = emf.createEntityManager();
try {
field.set (target, em);
em.getTransaction().begin();
Object result = m.invoke (target, args);
em.getTransaction().commit();
return result;
} catch (InvocationTargetException e) {
em.getTransaction().rollback();
throw e.getTargetException();
} catch (Exception e) {
throw new RuntimeException (e);
} finally {
em.close();
}
}
}

The idea is that you create it with an instance of say, a stateless session bean (SLSB) and an EntityManagerFactory, and subsequent invocations on the proxy will wrap calls to the real bean inside a transaction obtained from the EntityManagerFactory. For example:

Service service = (Service) TransactionProxy.newInstance (new ServiceBean(), emf);

This allows you to combine your beans in any number of transactional contexts. But it's not optimal since it's not actually using the @TransactionAttribute annotation of the classes under test. Hopefully, that's what the more elegant solution mentioned above is doing.

I'll update this tutorial after I've confirmed that. Until then, happy hacking!

5 comments:

Matthew said...

Hi,

when I tried adding the line:
jar-file ${project.build.directory}/classes
jar-file

with filtering set to true I get an IllegalArgumentException when the persistence.xml is parsed

Have you ever had this issue?

Bryan said...

Matthew,

The <testResources> section described above in the post needs to be placed in the <build> section of your pom.xml. Your file should be laid out like so:

project
modelVersion
groupId
artifactId
version
... dependencies and stuff here ...
build
testResources
... stuff including the filter ...

Hope that helps.

Rick_R said...

First, Thanks for you blog entry!

What's the best practice to avoid having to look up the EntityManager for every test class, since it's typically the same for all of the tests. I thought about using @BeforeClass in a Base class that your tests can extend.. and that sort of works ok I guess, but then in my subclasses if I want a BeforeClass method (for example for setting the em in my slsb), I have to manually call the BaseClass beforeClass method which I guess is ok, but maybe there's a better way?

I posted an example of what I'm talking about here:
http://pastie.org/292730

I'd like some feedback on the best approach. Seems like there should be a better approach than what I'm thinking of taking.

SRF said...

Thanks, this was helpful!

Kimble said...

Thanks! This was one of the more helpful articles I've read lately!