Summary
I'm seeing some odd behaviour when setting up unit tests using appengine's High Replication Datastore.
I've put a full example below. The issue is that a persisted object from one test case is available to a later test case, but only when the later test case retrieves it within a transaction. As I understand it, the tearDown method should be clearing the datastore entirely.
I assume there's something wrong with my setup, or I'm missing something significant. Please help.
Specific Question
Given the code below, why is the output as follows? (in particular the last line which I've bolded)
17-Sep-2013 20:41:35 org.datanucleus.PersistenceConfiguration setProperty
INFO: Property datanucleus.appengine.singletonPMFForName unknown - will be ignored
17-Sep-2013 20:41:36 com.google.appengine.datanucleus.MetaDataValidator validate
INFO: Performing appengine-specific metadata validation for com.test.Thing
17-Sep-2013 20:41:36 com.google.appengine.datanucleus.MetaDataValidator validate
INFO: Finished performing appengine-specific metadata validation for com.test.Thing
17-Sep-2013 20:41:36 com.google.appengine.api.datastore.dev.LocalDatastoreService init
INFO: Local Datastore initialized: Type: Master/Slave Storage: In-memory 17-Sep-2013 20:41:36 com.google.appengine.api.datastore.dev.LocalDatastoreService init
INFO: Local Datastore initialized: Type: Master/Slave Storage: In-memory
[OUTSIDE TRANSACTION] This correctly gets executed.
[INSIDE TRANSACTION] This should not be executed. THING:item
My comments
Unless I'm doing something very stupid, which is not unlikely, it appears that the item persisted in the first test case is still available to transactions in the second test case.
The second test case attempts to getObjectById outside of a transaction, and correctly throws an exception.
It then attempts the same within a transaction and retrieves an object that can only be the one persisted in the earlier test case, I think.
Why is this? What am I doing wrongly? Thanks very much in advance.
Code to replicate issue
I created a new Web Application Project in Eclipse, and added the classes and jars shown in this screenshot to it:
Class PMF looks like this:
package com.test;
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;
public final class PMF {
private static final PersistenceManagerFactory pmfInstance =
JDOHelper.getPersistenceManagerFactory("transactions-optional");
private PMF() {}
public static PMF getInstance() {
if (null == _instance) {
_instance = new PMF();
}
return _instance;
}
public static PersistenceManagerFactory get() {
return pmfInstance;
}
private static PMF _instance;
}
Class Thing looks like this:
package com.test;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
@PersistenceCapable
public class Thing {
public Thing(String item) {
this.item = item;
this.key = makeKey(item);
}
public static Key makeKey(String item) {
return KeyFactory.createKey("Thing", item);
}
public String getId() {
if (null == key) {
return null;
}
return KeyFactory.keyToString(key);
}
public String getItem() {
return item;
}
public String toString() {
return "THING:" + item;
}
@PrimaryKey
@Persistent
private Key key;
@Persistent
private String item;
}
And finally the TestDatastore class looks like this:
package com.test;
import javax.jdo.PersistenceManager;
import javax.jdo.Transaction;
import junit.framework.TestCase;
import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
public class TestDatastore extends TestCase {
@Override
public void setUp() {
helper.setUp();
}
@Override
public void tearDown() {
helper.tearDown();
}
public void testCreateThing() {
String item = "item";
Thing thing = new Thing(item);
PersistenceManager pm = PMF.get().getPersistenceManager();
Thing persisted = pm.makePersistent(thing);
Thing result = pm.getObjectById(Thing.class, persisted.getId());
assertEquals(item, result.getItem());
}
public void testThingDoesntExist() {
String item = "item";
Thing thing = new Thing(item);
PersistenceManager pm = PMF.get().getPersistenceManager();
Transaction tx = pm.currentTransaction();
try {
Thing testThing = pm.getObjectById(Thing.class, Thing.makeKey(thing.getItem()));
} catch (Exception e) {
System.out.println("[OUTSIDE TRANSACTION] This correctly gets executed.");
}
try {
tx.begin();
Thing testThing = pm.getObjectById(Thing.class, Thing.makeKey(thing.getItem()));
System.out.println("[INSIDE TRANSACTION] This should not be executed. " + testThing);
tx.commit();
} catch (Exception e) {
System.out.println("This doesn't get executed, but it should");
}
}
private final LocalServiceTestHelper helper =
new LocalServiceTestHelper(
new LocalDatastoreServiceTestConfig()
.setDefaultHighRepJobPolicyUnappliedJobPercentage(100));
}