Summary
I see some odd behavior when setting up unit tests using a high replication application data warehouse.
I gave a complete example below. The problem is that the saved object from one test case is available for a later test case, but only when a later test case retrieves it in a transaction. As I understand it, the tearDown method should completely clear the data store.
I assume something is wrong with my setup, or I'm missing something important. Please help.
Specific question
Given the code below, why is this the result as follows? (in particular, the last line highlighted by me)
17-Sep-2013 20:41:35 org.datanucleus.PersistenceConfiguration setProperty
INFO: the datanucleus.appengine.singletonPMFForName property is unknown - will be ignored
17-Sep-2013 20:41:36 com.google.appengine.datanucleus.MetaDataValidator validate
INFO: Performing application-specific metadata validation for com.test.Thing
17-Sep-2013 20:41:36 com.google.appengine.datanucleus.MetaDataValidator validate
INFO: Completing an application-specific metadata check for com.test.Thing
17-Sep-2013 20:41:36 com.google.appengine.api.datastore.dev.LocalDatastoreService init
INFO: local data storage initialized: Type: Master / Slave Storage: internal memory 17-Sep-2013 20:41:36 com.google.appengine.api.datastore.dev.LocalDatastoreService init
INFO: local data storage initialized: Type: Master / Slave Storage: in memory
[EXTERNAL OPERATION] This is done correctly.
[INSIDE. OPERATION] This should not be done. THING: item
My comments
If I do something very stupid, which is unlikely, it looks like the item saved in the first test case is still available for transactions in the second test case.
The second test case tries to getObjectById outside the transaction and correctly throws an exception.
Then it tries to do the same in the transaction and, it seems to me, returns an object that can only be the one that is saved in the previous case.
Why is this? What am I doing wrong? Thank you very much in advance.
Code to replicate the problem
I created a new web application project in Eclipse and added the classes and banks shown in this screenshot to it:
The PMF class is as follows:
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; }
The Thing class is as follows:
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)); }