Unit Testing Maven Based JPA Application on GAE

Posted on Monday, June 28, 2010

5


Recently, we started porting a complex enterprise timesheet and invoicing application to Google App Engine. We would talk about the strategy that we are following as a part of separate post but in this post let us look at how we can unit test JPA code effectively in our local environment.

By the way, the application that we are porting does not use JPA but it does use Spring. So we can make use of some of the Spring capabilities. The app engine does not store data in a relational format, however, it does support JPA 1.0. So we converted a simple Dao to JPADao and wanted to test it locally.

For local testing, you need some jars in the classpath which simulates the local runtime APIs for your tests. GAE. The jars that are required to be included in your pom.xml for local testing are

		<!--  Unit testing Jars -->
		<dependency>
			<groupid>com.google.appengine</groupid>
			<artifactid>appengine-testing</artifactid>
			<version>1.3.4</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupid>com.google.appengine</groupid>
			<artifactid>appengine-api-labs</artifactid>
			<version>1.3.4</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupid>com.google.appengine</groupid>
			<artifactid>appengine-api-stubs</artifactid>
			<version>1.3.4</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupid>junit</groupid>
			<artifactid>junit</artifactid>
			<version>4.8.1</version>
			<scope>test</scope>
		</dependency>

Now, you would notice that some/most of these jars are not available on the central maven repository or other repositories. So you would have to include them in your local repository or nexus if you have one.

You could include them one by one or using a shell script like this

#!/bin/sh

#
# This script installs the Google App Engine artifacts into you local Maven repository.
#
# If you would like to avoid the need for this step, ask Google by voting for the following issue:
#   http://code.google.com/p/googleappengine/issues/detail?id=1296
#

if [ "$1" == "" ]
then
    echo "usage: $0 /path/to/appengine-java-sdk-1.3.4"
    exit 1
fi

GAE_SDK_PATH=$1

mvn install:install-file -Dfile=$GAE_SDK_PATH/lib/user/appengine-api-1.0-sdk-1.3.4.jar -DgroupId=com.google.appengine -DartifactId=appengine-api-1.0-sdk -Dversion=1.3.4 -DgeneratePom=true -Dpackaging=jar

mvn install:install-file -Dfile=$GAE_SDK_PATH/lib/user/appengine-api-labs-1.3.4.jar -DgroupId=com.google.appengine -DartifactId=appengine-api-labs -Dversion=1.3.4 -DgeneratePom=true -Dpackaging=jar

mvn install:install-file -Dfile=$GAE_SDK_PATH/lib/impl/appengine-api-stubs.jar -DgroupId=com.google.appengine -DartifactId=appengine-api-stubs -Dversion=1.3.4 -DgeneratePom=true -Dpackaging=jar

mvn install:install-file -Dfile=$GAE_SDK_PATH/lib/testing/appengine-testing.jar -DgroupId=com.google.appengine -DartifactId=appengine-testing -Dversion=1.3.4 -DgeneratePom=true -Dpackaging=jar

mvn install:install-file -Dfile=$GAE_SDK_PATH/lib/user/orm/datanucleus-appengine-1.0.7.final.jar -DgroupId=com.google.appengine -DartifactId=datanucleus-appengine -Dversion=1.0.7 -DgeneratePom=true -Dpackaging=jar

mvn install:install-file -Dfile=$GAE_SDK_PATH/lib/user/orm/geronimo-jta_1.1_spec-1.1.1.jar -DgroupId=com.google.appengine -DartifactId=geronimo-jta_1.1_spec -Dversion=1.1.1 -DgeneratePom=true -Dpackaging=jar

If you refer to the GAE JPA documentation, another important step which has to be done is the enhancement of data classes. The DataNucleus implementation of JPA uses a post-compilation “enhancement” step in the build process to associate data classes with the JPA implementation.

Luckily, we have a maven plugin called maven-datanucleus-plugin which does the enhancement for us whenever any compilation takes place. Place that in your pom.xml

<plugin>
				<groupid>org.datanucleus</groupid>
				<artifactid>maven-datanucleus-plugin</artifactid>
				<version>1.1.4</version>
				<configuration>
					<mappingincludes>**/domain/*.class</mappingincludes>
					<verbose>true</verbose>
					<enhancername>ASM</enhancername>
					<api>JPA</api>
				</configuration>
				<executions>
					<execution>
						<phase>compile</phase>
						<goals>
							<goal>enhance</goal>
						</goals>
					</execution>
				</executions>
				<dependencies>
					<dependency>
						<groupid>org.datanucleus</groupid>
						<artifactid>datanucleus-core</artifactid>
						<version>1.1.5</version>
						<exclusions>
							<exclusion>
								<groupid>javax.transaction</groupid>
								<artifactid>transaction-api</artifactid>
							</exclusion>
						</exclusions>
					</dependency>
					<dependency>
						<groupid>org.datanucleus</groupid>
						<artifactid>datanucleus-rdbms</artifactid>
						<version>1.1.5</version>
					</dependency>
					<dependency>
						<groupid>org.datanucleus</groupid>
						<artifactid>datanucleus-enhancer</artifactid>
						<version>1.1.4</version>
					</dependency>
				</dependencies>
			</plugin>

Also include the relevant repositories for fetching this plugin.

Now our infrastructure for writing local tests is complete.

Let us look at two ways now for writing the unit tests.
One – Without using Spring
If your application does not use spring then you cannot use the JpaDaoSupport from spring. Here you should be able to use the EntityManager directly. So for instance you would have the EMF.java like this

public final class EMF {
	private static final EntityManagerFactory emfInstance = Persistence
			.createEntityManagerFactory("transactions-optional");

	private EMF() {
	}

	public static EntityManagerFactory get() {
		return emfInstance;
	}
}

And your tests would extend from LocalDatastoreTestCase

public abstract class LocalDatastoreTestCase extends TestCase {

	private final LocalServiceTestHelper helper = new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig());

	@Before
	public void setUp() {
		helper.setUp();
	}

	@After
	public void tearDown() {
		helper.tearDown();
	}
}

Now, the magic here happens in the the setUp method of the LocalServiceTestHelper which sets up the local runtime environment. LocalServiceTestHelper is setting up and tearing down the parts of the execution environment that are common to all local services, and LocalDatastoreServiceTestConfig is setting up and tearing down the parts of the execution environment that are specific to the local Datastore service.

Your test case could look like this

public class EmployeeDaoTest extends LocalDatastoreTestCase {

	@Test
	public void testShouldPersistEmployee() {
		EntityManager em = EMF.get().createEntityManager();
		Employee employee = new Employee();
		employee.setFirstName("Vikas");
		employee.setLastName("Hazrati");
		employee.setHireDate(new Date());

		em.persist(employee);

		Employee merge = em.merge(employee);

		Employee foundEmployee = em.find(Employee.class, merge.getKey());

		Assert.assertEquals(employee.getFirstName(), foundEmployee.getFirstName());

	}

Two – With Spring
If you are using Spring then you do not need an explicit handle on the EntityManager. The base test case could look like this

@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners( { DependencyInjectionTestExecutionListener.class })
@ContextConfiguration(locations = { "classpath:test-applicationContext.xml", "classpath:test-applicationContext-dao.xml"})
public abstract class LocalDatastoreSpringTestCase extends TestCase {

	private final LocalServiceTestHelper helper = new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig());

	@Before
	public void setUp() {
		helper.setUp();
	}

	@After
	public void tearDown() {
		helper.tearDown();
	}
}

and the corresponding test would look like a regular JPA based test within spring

public class EmployeeDaoSpringTest extends LocalDatastoreSpringTestCase {

	@Autowired
	private EmployeeDao employeeDao;

	@Test
	public void testShouldPersistEmployee() {
		Employee employee = new Employee();
		employee.setFirstName("Vikas");
		employee.setLastName("Hazrati");
		employee.setHireDate(new Date());

		employeeDao.createEmployee(employee);

		Collection<employee> list = employeeDao.list();

		Assert.assertEquals(1, list.size());
	}

Thus, once you set up the basic infrastructure, which is a bit tricky considering the dependencies which have to be included in your pom.xml, the actual tests are similar to what you have been writing for testing DAOs.
The entire code for the sample application is present here, on google code for your reference.

Advertisements
Posted in: Java