Friday, October 30, 2009

Unit Testing EJBs and JPA with Embeddable GlassFish

Java EE 6 has been a topic here at CTP for quite a while now. During summer I had the pleasure of conducting an internship which was targeted to explore the possibilities of the upcoming standard, namely EJB 3.1, JPA 2.0, JAX-RS, JSF 2.0 and JCDI on top of GlassFish v3 - and it turned out surprisingly productive, although all implementations were far from being in a release state!

Only one thing we failed to run in a stable way: The new EJB 3.1 container API refused to run our unit tests. The main problem was JPA support - persistence units got not recognized and EJBs usually failed with a NullPointerException calling the EntityManager.

Alexis and Adam recently blogged about this specific feature, and of course I had to get back and try it out myself! Indeed, starting up a container and looking up EJBs works fine. But to test your Entities there are still a few tweaks you might want to apply.

Start with creating a Maven project and add the embeddable GlassFish dependency. I'm also using TestNG instead of JUnit, as it has a nice @BeforeSuite annotation which allows starting the container only once before running your tests.

<dependency>
<groupId>org.glassfish.extras</groupId>
<artifactId>glassfish-embedded-all</artifactId>
<version>3.0-b69</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>5.9</version>
<scope>test</scope>
<classifier>jdk15</classifier>
</dependency>

As described in Alexis' blog, you can start your EJBContainer with a reference to your GlassFish domain. This will allow you to start up data sources you most probably need to properly test your JPA code.

The disadvantage here is that you either depend on a hardcoded location or a system property which each of your team members have to set. Or, in case of your continuous integration system, you might not want to have a GlassFish installation at all.

Fortunately you can create a mini GlassFish domain with only a few files. The image below shows the files you need and how I placed them in my Maven module:



You can take your existing domain.xml containing your data sources and place it in here - you can reference it now relatively to your module location. Your unit tests then start with:

private static Context ctx;
private static EJBContainer container;

@BeforeSuite
public static void createContainer() {
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(EJBContainer.MODULES, new File("target/classes");
properties.put("org.glassfish.ejb.embedded.glassfish.installation.root",
"./src/test/glassfish");
container = EJBContainer.createEJBContainer(properties);
ctx = container.getContext();
}

This will run your unit tests against your development database. In case you want to run them in a local database, you can simple replace the connection pool config in your domain.xml, e.g. with a local Derby installation:

<jdbc-connection-pool datasource-classname="org.apache.derby.jdbc.EmbeddedDataSource"
res-type="javax.sql.DataSource" name="[your DS name]" ping="true">
<property name="ConnectionAttributes" value="create=true" />
<property name="DatabaseName" value="./target/unit-test" />
<property name="Password" value="" />
<property name="User" value="" />
</jdbc-connection-pool>

This creates the database in your target folder and requires adding Derby to your Maven POM:

<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>10.5.3.0_1</version>
<scope>test</scope>
</dependency>

Unfortunately this setup might not match with the configuration in your persistence.xml and generate invalid SQL for your test database. You can either solve this with Maven filters in different profiles, or alternatively create a staging directory for your EJBContainer. I'm using the Apache Commons IO tools here for convenience:

...
private static final String MODULE_NAME = "embedded";
private static final String TARGET_DIR = "target/" + MODULE_NAME;

@BeforeSuite
public static void createContainer() throws Exception {
File target = prepareModuleDirectory();
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(EJBContainer.MODULES, target);
properties.put("org.glassfish.ejb.embedded.glassfish.installation.root",
"./src/test/glassfish");
...
}

private static File prepareModuleDirectory() throws IOException {
File result = new File(TARGET_DIR);
FileUtils.copyDirectory(new File("target/classes"), result);
FileUtils.copyFile(new File("target/test-classes/META-INF/persistence.xml"),
new File(TARGET_DIR + "/META-INF/persistence.xml"));
return result;
}

You can use the @AfterSuite annotation to clean up the temporary folder. Note that with this setup, the EJB lookups change:

protected <T> T lookupBy(Class<T> type) throws NamingException {
return (T) ctx.lookup("java:global/" + MODULE_NAME + "/"
+ type.getSimpleName());
}

1 Kommentare:

majson said...

Another interesting approach to testing JPA queries is a stack called Unitils (http://unitils.org/summary.html). Nicely integrated with TestNG / JUnit and DBUnit but offers much more.

I'm using it in my projects and works just perfect.