Wednesday, May 6, 2009

JDO and unit tests

Building App Engine apps with Java and GWT has become relatively easy, thanks to the great tool support with the eclipse plugin. Building these applications in a way that everything is easily testable is a little bit more work. The good news: once one has formed a habit of coding with tests in mind and learned a couple patterns on how to do it, it easily becomes second nature and produces leaner, cleaner, and more reliable software. On this blog, I occasionally post articles that focus a little more on that aspect of building App Engine apps (I'm starting to label them from now on for easier discoverability). This article is part of that series. Any code quoted in this post can be downloaded as open source and used under Apache license in any project. I would like to thank Max Ross, who provided a lot of inspiration in the unit tests for datanucleus-appengine and was also very helpful with advice as I was running into a couple of walls along the way :-)

One of the many things I liked about the Java App Engine launch is that right from the start, the team published Information on how to set up an App Engine based unit test. The recipe in those tests worked very well for me, especially since I was mostly working with the lower level DatastoreService and MemcacheService, not the standards based equivalents. Recently however, I wanted to test a class that uses JDO, so I needed a PersistenceManagerFactory. Getting such an object is not quite as straightforward: as documented, only one factory object per entire App Engine application should ever exist. If I try to load the factory more than once (like, say, once for each unit test), the system will actually throw an exception. Also, do I always need a jdoconfig.xml in my classpath? Turns out that the solution is not as complex as I thought at first. It took me a couple of attempts to get the setup right though, so I thought I'd share the recipe on my blog. As mentioned before, if you want to use these classes straight out of the box, you can also download them as a jar file.

Assume we have a class TestEnvironment, just like in the official instructions. With this class, we can create a straightforward TestInitializer class that can prepare an App Engine environment for us:

public class TestInitializer {

private final ApiProxy.Environment environment;

private PersistenceManagerFactory pmf;

/**
* Constructor
*
* @param environmentOrNull
* the environment that should be used for the
* test (null to use the default)
*/
public TestInitializer(
ApiProxy.Environment environmentOrNull) {
this.environment =
(environmentOrNull == null) ? new TestEnvironment()
: environmentOrNull;
}

/** Constructor with default parameters */
public TestInitializer() {
this(null);
}

/**
* Sets up a unit test with the options stored in this
* object. This method should only be called once per
* object.
*/
public void setUp() throws Exception {

ApiProxyLocalImpl proxy =
new ApiProxyLocalImpl(new File(".")) {
};
proxy.setProperty(
LocalDatastoreService.NO_STORAGE_PROPERTY,
Boolean.TRUE.toString());
ApiProxy.setDelegate(proxy);
ApiProxy.setEnvironmentForCurrentThread(environment);
}
}


For the tearDown (cleanup after a unit test), I found a useful trick in the JDOTestCase class. Successful tests are expected to clean up after themselves and not leave any open transactions behind, which is verified by the following code:

  public void tearDown(boolean testWasSuccessful)
throws Exception {
Transaction txn =
DatastoreServiceFactory.getDatastoreService()
.getCurrentTransaction(null);
try {
if (txn != null) {
try {
txn.rollback();
} finally {
if (testWasSuccessful) {
throw new IllegalStateException(
"Datastore service still has an active txn. Please "
+ "rollback or commit all txns before test completes.");
}
}
}
} finally {
ApiProxy.clearEnvironmentForCurrentThread();
}
}


So far, so good -- but what about JDO? As it turns out, javax.jdo.JDOHelper has a very useful utility method. It constructs a new PersistenceManagerFactory from a Properties object instead of the jdoconfig.xml This might be highly inefficient in production, but for unit tests, it completely bypasses the create-only-one-factory-ever rule :-). The following tool method makes use of this trick:

  private PersistenceManagerFactory pmf;

public PersistenceManagerFactory getPersistenceManagerFactory() {
if (pmf == null) {
Properties newProperties = new Properties();
newProperties
.put(
"javax.jdo.PersistenceManagerFactoryClass",
"org.datanucleus.store.appengine.jdo."
+ DatastoreJDOPersistenceManagerFactory");
newProperties.put("javax.jdo.option.ConnectionURL",
"appengine");
newProperties.put(
"javax.jdo.option.NontransactionalRead", "true");
newProperties.put(
"javax.jdo.option.NontransactionalWrite", "true");
newProperties.put("javax.jdo.option.RetainValues",
"true");
newProperties.put(
"datanucleus.appengine.autoCreateDatastoreTxns",
"true");
newProperties.put(
"datanucleus.appengine.autoCreateDatastoreTxns",
"true");
pmf =
JDOHelper
.getPersistenceManagerFactory(newProperties);
}
return pmf;
}


Now that our TestInitializer is complete, it is easy to wrap it in a unit test. You can look at the entire code here, so suffice it to say that I create an abstract BaseTest that uses the initializer above and also provides a convenience method that gets me a new PersistenceManager object:

  public PersistenceManager newPersistenceManager() {
return initializer.getPersistenceManagerFactory()
.getPersistenceManager();
}


Assumed we have a simple data class similar to the Employee class. The following unit test is a simple example that simulates storing and loading objects from the store using JDO:

package com.appenginefan.toolkit.unittests;

import java.util.Date;
import javax.jdo.PersistenceManager;

public class SimpleJDOTest
extends BaseTest {

public void testSetAndGet() {

// Create a new employee and write the object to the
// store
final Date date = new Date();
EmployeeData employee =
new EmployeeData("John", "Doe", date);
PersistenceManager manager = newPersistenceManager();
final Long id =
manager.makePersistent(employee).getId();
manager.close();

// From a different persistence manager, look up the
// object
manager = newPersistenceManager();
employee =
manager.getObjectById(EmployeeData.class, id);
assertEquals(employee.getFirstName(), "John");
assertEquals(employee.getLastName(), "Doe");
manager.close();
}
}


As shown above, once the BaseTest class is written that does all the setup one needs (and which can be reused throughout the projects), adding new persistence related tests is no longer that cumbersome. And when it's easy to write good tests, it usually is also more fun -- which means, more tests get written and more bugs detected before code goes into production.

20 comments:

brendan said...

Thanks for this. I had just figured out that the google examples only worked for the low level datastore api, some I'm really happy someone found the way to do it with JDO.

brendan said...

Thank you for this.

I was trying to figure how to write test cases for JDO on appengine, and you've posted the solution.

Alexander said...

Thank you for the code - I was able to run it but I wonder why the following test fails (I've added it to the end of your tests):

manager = newPersistenceManager();
Query query = manager.newQuery(Employee.class);
List employees = (List) query.execute();
assertFalse(employees.isEmpty());
manager.close();

Mickaƫl said...

Thank you very much, this is very useful !!!

The App Engine Fan said...

> I was able to run it but I wonder why the following test fails

Hi,

Sorry, my workstation is not hooked up (just moved); can you post the exception to the blog? Maybe I'll see from the stack trace what's going on?

Cheers,

Jens

samabhik said...

Hello,

I tried playing with your BaseTest class and was trying to makePersist an object Book which has a owned bi-directional relationship with object Chapter. The example can be taken from http://gae-java-persistence.blogspot.com/2009/10/creating-bidrectional-owned-one-to-many.html

but I am getting the following errors -

java.lang.AbstractMethodError: com.appenginefan.toolkit.unittests.TestEnvironment.getAttributes()Ljava/util/Map;
at com.google.appengine.api.NamespaceManager.get(NamespaceManager.java:97)
at com.google.appengine.api.datastore.DatastoreApiHelper.getCurrentAppIdNamespace(DatastoreApiHelper.java:85)
at com.google.appengine.api.datastore.DatastoreApiHelper.getCurrentAppIdNamespace(DatastoreApiHelper.java:77)
at com.google.appengine.api.datastore.Key.(Key.java:100)
at com.google.appengine.api.datastore.Key.(Key.java:85)
at com.google.appengine.api.datastore.Key.(Key.java:81)
at com.google.appengine.api.datastore.Entity.(Entity.java:115)
at com.google.appengine.api.datastore.Entity.(Entity.java:96)
at org.datanucleus.store.appengine.DatastoreFieldManager.(DatastoreFieldManager.java:167)
at org.datanucleus.store.appengine.DatastorePersistenceHandler.insertPreProcess(DatastorePersistenceHandler.java:316)
at org.datanucleus.store.appengine.DatastorePersistenceHandler.insertObjects(DatastorePersistenceHandler.java:236)
at org.datanucleus.store.appengine.DatastorePersistenceHandler.insertObject(DatastorePersistenceHandler.java:225)
at org.datanucleus.state.JDOStateManagerImpl.internalMakePersistent(JDOStateManagerImpl.java:3185)
at org.datanucleus.state.JDOStateManagerImpl.makePersistent(JDOStateManagerImpl.java:3161)
at org.datanucleus.ObjectManagerImpl.persistObjectInternal(ObjectManagerImpl.java:1298)
at org.datanucleus.ObjectManagerImpl.persistObject(ObjectManagerImpl.java:1175)
at org.datanucleus.jdo.JDOPersistenceManager.jdoMakePersistent(JDOPersistenceManager.java:669)
at org.datanucleus.jdo.JDOPersistenceManager.makePersistent(JDOPersistenceManager.java:694)
at com.gopono.ds.DiaryTest.testSetAndGet(DiaryTest.java:30)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at junit.framework.TestCase.runTest(TestCase.java:164)
at com.appenginefan.toolkit.unittests.BaseTest.runTest(BaseTest.java:71)
at junit.framework.TestCase.runBare(TestCase.java:130)
at junit.framework.TestResult$1.protect(TestResult.java:106)
at junit.framework.TestResult.runProtected(TestResult.java:124)
at junit.framework.TestResult.run(TestResult.java:109)
at junit.framework.TestCase.run(TestCase.java:120)
at junit.framework.TestSuite.runTest(TestSuite.java:230)
at junit.framework.TestSuite.run(TestSuite.java:225)
at org.eclipse.jdt.internal.junit.runner.junit3.JUnit3TestReference.run(JUnit3TestReference.java:130)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

I tried using Long for a Key instead of a Key object, but that didn't work either. Could you please verify?

The App Engine Fan said...

Hi,

I'll take a look over the next days and answer on this thread. Sorry for the delay.

Cheers,

Jens

The App Engine Fan said...

Hi,

I just committed a change to aeftools that should fix this (see http://code.google.com/p/aeftools/source/detail?r=20 ). Let me know if that does not address your problem.

Best regards,

Jens

SidneyShek said...

This is great stuff!
I did have a problem getting the BaseTest class working on AppEngine 1.3.2 (got an IllegalAccessException on ApiProxyLocalImpl in TestInitializer). Looks like visibility on that class has been reduced. I just changed the setUp() method in TestInitializer from instantiating ApiProxyLocalImpl directly to using an ApiProxyLocalFactory. Something like:

ApiProxyLocalFactory factory = new ApiProxyLocalFactory();
ApiProxyLocal proxy = factory.create(new LocalServerEnvironment()
{

public void waitForServerToStart() throws InterruptedException{}

public int getPort(){
return 0;
}

public File getAppDir() {
return new File(".");
}

public String getAddress(){
return null;
}
});

Marcos V Soares said...

First of all, great tutorial. One question: I'm using app engine 1.3.3 and the class ApiProxyLocalImpl is not public, so this does not work anymore. Any workaround? Thanks

Carlos Eduardo said...

I had to changed the code like SidneyShek said, but if I extend BaseTest I get this junit error: "No tests found". If I remove "extends BaseTest", then the test runs (and fails).

Hiperion said...

Have you got the source code of TestInitializer? I don´t understand very well your modifications in it (ApiProxyLocal proxy = factory.create(new LocalServerEnvironment()....etc).

Hiperion said...

SidneyShek, Can you send me the source code with the modification I can´t understand very well.

Carlos Eduardo said...

Even though I'm using Junit 4, I had to follow the convention to begin test method names with "test".

Now I'm wondering why the getObjectById returns an object with null fields.

Carlos Eduardo said...

Now it is working for me. The problem was final fields are not persistent even if they are annotated with @Persistent

But there is a much simpler solution. Just write at the beginning of the TestSuite:

private static final LocalServiceTestHelper HELPER = new LocalServiceTestHelper(
new LocalDatastoreServiceTestConfig());

@Before
public static void setUpClass() {
HELPER.setUp();
}

@After
public static void tearDownClass() {
HELPER.tearDown();
}
}

Blogger said...

Thanks! Finally, I can test JDO on Appengine :D

SidneyShek thank you also :D

allan kelly said...

Thanks for the advice, its been a great help.
It hasn't worked out-of-the-box for me, I think that might be because Google have moved on to 1.3.5 and a few things have changed.

First off I had to include SidneyShek changes.

Next I hit a "java.lang.NullPointerException: No API environment is registered for this thread" error.

That was fixed with adding
LocalServiceTestHelper lsth = new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig());

to BaseTest, plus help setUp and tear in BaseTest - I'm not sure where they got lost be those calls seem to be missing in the sample code.

Finally, I got a "The API Package datastore_v3 ... was not found"
There seems to be some threading issue here which I'm not sure of.

This thread (different subject) proved useful:
http://groups.google.com/group/google-appengine-java/browse_thread/thread/90f60cc91cce5a9b?fwc=2

To TestInitializer.setUp I added:
proxy.setProperty(LocalDatastoreService.NO_STORAGE_PROPERTY, Boolean.TRUE.toString());

It all seems to be working now, feels slightly hacky but I wanted to record these finding before I forgot what I'd done.

Tk said...

Thanks for this. Very nice job, was the only working solution...respect man

june said...

Exactly the info I was looking for, thanks for sharing.

Tim H said...

Creating a separate PersistenceManager isn't necessary anymore.

You can simply use the LocalServiceTestHelper class loaded with the LocalDatastoreServiceTestConfig configuration to setUp() and tearDown() an in-memory datastore that is also used by the persistence manager layer.

Source: http://whileonefork.blogspot.com/2010/10/google-appengine-junit-tests-that-use.html