diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 00000000000..7a63b1470de
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,6 @@
+language: java
+
+env:
+ - TEST_DIR=unittests
+
+script: cd $TEST_DIR && mvn test
diff --git a/unittests/README.md b/unittests/README.md
new file mode 100644
index 00000000000..e1ecb197f0f
--- /dev/null
+++ b/unittests/README.md
@@ -0,0 +1,5 @@
+# appengine-ndb-snippets
+
+## unittests
+
+This subdirectory contains code snippets for [Local Unit Testing for Java](https://cloud.google.com/appengine/docs/java/tools/localunittesting).
diff --git a/unittests/pom.xml b/unittests/pom.xml
new file mode 100644
index 00000000000..64ec82809d2
--- /dev/null
+++ b/unittests/pom.xml
@@ -0,0 +1,139 @@
+
+
+
+ 4.0.0
+ war
+ 1.0-SNAPSHOT
+
+ com.google.appengine.samples.unittest
+ unittests
+
+
+ 1
+ UTF-8
+
+
+
+ 3.1.0
+
+
+
+
+
+ com.google.appengine
+ appengine-api-1.0-sdk
+ 1.9.18
+
+
+ javax.servlet
+ servlet-api
+ 2.5
+ provided
+
+
+ jstl
+ jstl
+ 1.2
+
+
+
+
+ junit
+ junit
+ 4.12
+ test
+
+
+ com.google.appengine
+ appengine-testing
+ 1.9.18
+ test
+
+
+ com.google.appengine
+ appengine-api-stubs
+ 1.9.18
+ test
+
+
+
+
+
+ ${project.build.directory}/${project.build.finalName}/WEB-INF/classes
+
+
+ org.codehaus.mojo
+ versions-maven-plugin
+ 2.1
+
+
+ compile
+
+ display-dependency-updates
+ display-plugin-updates
+
+
+
+
+
+ org.apache.maven.plugins
+ 3.1
+ maven-compiler-plugin
+
+
+ 1.7
+
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+ 2.4
+
+ true
+
+
+
+ ${basedir}/src/main/webapp/WEB-INF
+ true
+ WEB-INF
+
+
+
+
+
+
+ com.google.appengine
+ appengine-maven-plugin
+ 1.9.18
+
+ false
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ 2.15
+
+
+ checkstyle
+ validate
+
+ check
+
+
+ true
+
+
+
+
+
+
+
diff --git a/unittests/src/main/webapp/WEB-INF/appengine-web.xml b/unittests/src/main/webapp/WEB-INF/appengine-web.xml
new file mode 100644
index 00000000000..8bf645d1013
--- /dev/null
+++ b/unittests/src/main/webapp/WEB-INF/appengine-web.xml
@@ -0,0 +1,10 @@
+
+
+ your-app-id
+ ${appengine.app.version}
+ true
+
+
+
+
+
diff --git a/unittests/src/main/webapp/WEB-INF/logging.properties b/unittests/src/main/webapp/WEB-INF/logging.properties
new file mode 100644
index 00000000000..a17206681f0
--- /dev/null
+++ b/unittests/src/main/webapp/WEB-INF/logging.properties
@@ -0,0 +1,13 @@
+# A default java.util.logging configuration.
+# (All App Engine logging is through java.util.logging by default).
+#
+# To use this configuration, copy it into your application's WEB-INF
+# folder and add the following to your appengine-web.xml:
+#
+#
+#
+#
+#
+
+# Set the default logging level for all loggers to WARNING
+.level = WARNING
diff --git a/unittests/src/main/webapp/WEB-INF/web.xml b/unittests/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 00000000000..28bf558b8fa
--- /dev/null
+++ b/unittests/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/unittests/src/test/java/com/google/appengine/samples/unittest/AuthenticationTest.java b/unittests/src/test/java/com/google/appengine/samples/unittest/AuthenticationTest.java
new file mode 100644
index 00000000000..414562eb0b0
--- /dev/null
+++ b/unittests/src/test/java/com/google/appengine/samples/unittest/AuthenticationTest.java
@@ -0,0 +1,36 @@
+package com.google.appengine.samples.unittest;
+
+// [START auth]
+import com.google.appengine.api.users.UserService;
+import com.google.appengine.api.users.UserServiceFactory;
+import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
+import com.google.appengine.tools.development.testing.LocalUserServiceTestConfig;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class AuthenticationTest {
+
+ private final LocalServiceTestHelper helper =
+ new LocalServiceTestHelper(new LocalUserServiceTestConfig())
+ .setEnvIsAdmin(true).setEnvIsLoggedIn(true);
+
+ @Before
+ public void setUp() {
+ helper.setUp();
+ }
+
+ @After
+ public void tearDown() {
+ helper.tearDown();
+ }
+
+ @Test
+ public void testIsAdmin() {
+ UserService userService = UserServiceFactory.getUserService();
+ assertTrue(userService.isUserAdmin());
+ }
+}
+// [END auth]
diff --git a/unittests/src/test/java/com/google/appengine/samples/unittest/CapabilitiesTest.java b/unittests/src/test/java/com/google/appengine/samples/unittest/CapabilitiesTest.java
new file mode 100644
index 00000000000..eac6a40516a
--- /dev/null
+++ b/unittests/src/test/java/com/google/appengine/samples/unittest/CapabilitiesTest.java
@@ -0,0 +1,41 @@
+package com.google.appengine.samples.unittest;
+
+// [START local_capabilities]
+import com.google.appengine.api.capabilities.Capability;
+import com.google.appengine.api.capabilities.CapabilityStatus;
+import com.google.appengine.api.datastore.DatastoreService;
+import com.google.appengine.api.datastore.DatastoreServiceFactory;
+import com.google.appengine.api.datastore.FetchOptions;
+import com.google.appengine.api.datastore.Query;
+import com.google.appengine.tools.development.testing.LocalCapabilitiesServiceTestConfig;
+import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
+import com.google.apphosting.api.ApiProxy;
+import org.junit.After;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class CapabilitiesTest {
+
+ private LocalServiceTestHelper helper;
+
+ @After
+ public void tearDown() {
+ helper.tearDown();
+ }
+
+ @Test(expected = ApiProxy.CapabilityDisabledException.class)
+ public void testDisabledDatastore() {
+ Capability testOne = new Capability("datastore_v3");
+ CapabilityStatus testStatus = CapabilityStatus.DISABLED;
+ //Initialize
+ LocalCapabilitiesServiceTestConfig config =
+ new LocalCapabilitiesServiceTestConfig().setCapabilityStatus(testOne, testStatus);
+ helper = new LocalServiceTestHelper(config);
+ helper.setUp();
+ FetchOptions fo = FetchOptions.Builder.withLimit(10);
+ DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
+ assertEquals(0, ds.prepare(new Query("yam")).countEntities(fo));
+ }
+}
+// [END local_capabilities]
diff --git a/unittests/src/test/java/com/google/appengine/samples/unittest/DeferredTaskTest.java b/unittests/src/test/java/com/google/appengine/samples/unittest/DeferredTaskTest.java
new file mode 100644
index 00000000000..3b103732878
--- /dev/null
+++ b/unittests/src/test/java/com/google/appengine/samples/unittest/DeferredTaskTest.java
@@ -0,0 +1,58 @@
+package com.google.appengine.samples.unittest;
+
+// [START taskqueue_example_2]
+import com.google.appengine.api.taskqueue.DeferredTask;
+import com.google.appengine.api.taskqueue.QueueFactory;
+import com.google.appengine.api.taskqueue.TaskOptions;
+import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
+import com.google.appengine.tools.development.testing.LocalTaskQueueTestConfig;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.*;
+
+public class DeferredTaskTest {
+
+ // Unlike CountDownLatch, TaskCountDownlatch lets us reset.
+ private final LocalTaskQueueTestConfig.TaskCountDownLatch latch =
+ new LocalTaskQueueTestConfig.TaskCountDownLatch(1);
+
+ private final LocalServiceTestHelper helper =
+ new LocalServiceTestHelper(new LocalTaskQueueTestConfig()
+ .setDisableAutoTaskExecution(false)
+ .setCallbackClass(LocalTaskQueueTestConfig.DeferredTaskCallback.class)
+ .setTaskExecutionLatch(latch));
+
+ private static class MyTask implements DeferredTask {
+ private static boolean taskRan = false;
+
+ @Override
+ public void run() {
+ taskRan = true;
+ }
+ }
+
+ @Before
+ public void setUp() {
+ helper.setUp();
+ }
+
+ @After
+ public void tearDown() {
+ MyTask.taskRan = false;
+ latch.reset();
+ helper.tearDown();
+ }
+
+ @Test
+ public void testTaskGetsRun() throws InterruptedException {
+ QueueFactory.getDefaultQueue().add(
+ TaskOptions.Builder.withPayload(new MyTask()));
+ assertTrue(latch.await(5, TimeUnit.SECONDS));
+ assertTrue(MyTask.taskRan);
+ }
+}
+// [END taskqueue_example_2]
diff --git a/unittests/src/test/java/com/google/appengine/samples/unittest/LocalCustomPolicyHighRepDatastoreTest.java b/unittests/src/test/java/com/google/appengine/samples/unittest/LocalCustomPolicyHighRepDatastoreTest.java
new file mode 100644
index 00000000000..e56eaf5855f
--- /dev/null
+++ b/unittests/src/test/java/com/google/appengine/samples/unittest/LocalCustomPolicyHighRepDatastoreTest.java
@@ -0,0 +1,63 @@
+package com.google.appengine.samples.unittest;
+
+import com.google.appengine.api.datastore.DatastoreService;
+import com.google.appengine.api.datastore.DatastoreServiceFactory;
+import com.google.appengine.api.datastore.Entity;
+import com.google.appengine.api.datastore.Key;
+import com.google.appengine.api.datastore.Query;
+import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
+import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static com.google.appengine.api.datastore.FetchOptions.Builder.withLimit;
+import static org.junit.Assert.*;
+
+// [START HRD_example_2]
+// ...
+import com.google.appengine.api.datastore.dev.HighRepJobPolicy;
+
+public class LocalCustomPolicyHighRepDatastoreTest {
+ private static final class CustomHighRepJobPolicy implements HighRepJobPolicy {
+ static int count = 0;
+ @Override
+ public boolean shouldApplyNewJob(Key entityGroup) {
+ // every other new job fails to apply
+ return count++ % 2 == 0;
+ }
+
+ @Override
+ public boolean shouldRollForwardExistingJob(Key entityGroup) {
+ // every other existing job fails to apply
+ return count++ % 2 == 0;
+ }
+ }
+
+ private final LocalServiceTestHelper helper =
+ new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig()
+ .setAlternateHighRepJobPolicyClass(CustomHighRepJobPolicy.class));
+
+ @Before
+ public void setUp() {
+ helper.setUp();
+ }
+
+ @After
+ public void tearDown() {
+ helper.tearDown();
+ }
+
+ @Test
+ public void testEventuallyConsistentGlobalQueryResult() {
+ DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
+ ds.put(new Entity("yam")); // applies
+ ds.put(new Entity("yam")); // does not apply
+ // first global query only sees the first Entity
+ assertEquals(1, ds.prepare(new Query("yam")).countEntities(withLimit(10)));
+ // second global query sees both Entities because we "groom" (attempt to
+ // apply unapplied jobs) after every query
+ assertEquals(2, ds.prepare(new Query("yam")).countEntities(withLimit(10)));
+ }
+}
+// [END HRD_example_2]
diff --git a/unittests/src/test/java/com/google/appengine/samples/unittest/LocalDatastoreTest.java b/unittests/src/test/java/com/google/appengine/samples/unittest/LocalDatastoreTest.java
new file mode 100644
index 00000000000..6ae5ee2117a
--- /dev/null
+++ b/unittests/src/test/java/com/google/appengine/samples/unittest/LocalDatastoreTest.java
@@ -0,0 +1,52 @@
+package com.google.appengine.samples.unittest;
+
+// [START datastore_example_1]
+
+import com.google.appengine.api.datastore.DatastoreService;
+import com.google.appengine.api.datastore.DatastoreServiceFactory;
+import com.google.appengine.api.datastore.Entity;
+import com.google.appengine.api.datastore.Query;
+import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
+import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static com.google.appengine.api.datastore.FetchOptions.Builder.withLimit;
+import static org.junit.Assert.*;
+
+public class LocalDatastoreTest {
+
+ private final LocalServiceTestHelper helper = new
+ LocalServiceTestHelper(new LocalDatastoreServiceTestConfig());
+
+ @Before
+ public void setUp() {
+ helper.setUp();
+ }
+
+ @After
+ public void tearDown() {
+ helper.tearDown();
+ }
+
+ // run this test twice to prove we're not leaking any state across tests
+ private void doTest() {
+ DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
+ assertEquals(0, ds.prepare(new Query("yam")).countEntities(withLimit(10)));
+ ds.put(new Entity("yam"));
+ ds.put(new Entity("yam"));
+ assertEquals(2, ds.prepare(new Query("yam")).countEntities(withLimit(10)));
+ }
+
+ @Test
+ public void testInsert1() {
+ doTest();
+ }
+
+ @Test
+ public void testInsert2() {
+ doTest();
+ }
+}
+// [END datastore_example_1]
\ No newline at end of file
diff --git a/unittests/src/test/java/com/google/appengine/samples/unittest/LocalHighRepDatastoreTest.java b/unittests/src/test/java/com/google/appengine/samples/unittest/LocalHighRepDatastoreTest.java
new file mode 100644
index 00000000000..8dbbe8afb0b
--- /dev/null
+++ b/unittests/src/test/java/com/google/appengine/samples/unittest/LocalHighRepDatastoreTest.java
@@ -0,0 +1,48 @@
+package com.google.appengine.samples.unittest;
+
+// [START HRD_example_1]
+import com.google.appengine.api.datastore.DatastoreService;
+import com.google.appengine.api.datastore.DatastoreServiceFactory;
+import com.google.appengine.api.datastore.Entity;
+import com.google.appengine.api.datastore.Key;
+import com.google.appengine.api.datastore.KeyFactory;
+import com.google.appengine.api.datastore.Query;
+import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
+import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static com.google.appengine.api.datastore.FetchOptions.Builder.withLimit;
+import static org.junit.Assert.*;
+
+public class LocalHighRepDatastoreTest {
+
+ // maximum eventual consistency
+ private final LocalServiceTestHelper helper =
+ new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig()
+ .setDefaultHighRepJobPolicyUnappliedJobPercentage(100));
+
+ @Before
+ public void setUp() {
+ helper.setUp();
+ }
+
+ @After
+ public void tearDown() {
+ helper.tearDown();
+ }
+
+ @Test
+ public void testEventuallyConsistentGlobalQueryResult() {
+ DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
+ Key ancestor = KeyFactory.createKey("foo", 3);
+ ds.put(new Entity("yam", ancestor));
+ ds.put(new Entity("yam", ancestor));
+ // global query doesn't see the data
+ assertEquals(0, ds.prepare(new Query("yam")).countEntities(withLimit(10)));
+ // ancestor query does see the data
+ assertEquals(2, ds.prepare(new Query("yam", ancestor)).countEntities(withLimit(10)));
+ }
+}
+// [END HRD_example_1]
diff --git a/unittests/src/test/java/com/google/appengine/samples/unittest/LocalMemcacheTest.java b/unittests/src/test/java/com/google/appengine/samples/unittest/LocalMemcacheTest.java
new file mode 100644
index 00000000000..0e98524ae0c
--- /dev/null
+++ b/unittests/src/test/java/com/google/appengine/samples/unittest/LocalMemcacheTest.java
@@ -0,0 +1,51 @@
+package com.google.appengine.samples.unittest;
+
+// [START memcache_example_1]
+import com.google.appengine.api.memcache.MemcacheService;
+import com.google.appengine.api.memcache.MemcacheServiceFactory;
+import com.google.appengine.tools.development.testing.LocalMemcacheServiceTestConfig;
+import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
+// [END memcache_example_1]
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+// [START memcache_example_2]
+public class LocalMemcacheTest {
+
+ private final LocalServiceTestHelper helper =
+ new LocalServiceTestHelper(new LocalMemcacheServiceTestConfig());
+ // [END memcache_example_2]
+
+ @Before
+ public void setUp() {
+ helper.setUp();
+ }
+
+ @After
+ public void tearDown() {
+ helper.tearDown();
+ }
+
+ // run this test twice to prove we're not leaking any state across tests
+ private void doTest() {
+ // [START memcache_example_3]
+ MemcacheService ms = MemcacheServiceFactory.getMemcacheService();
+ assertFalse(ms.contains("yar"));
+ ms.put("yar", "foo");
+ assertTrue(ms.contains("yar"));
+ // [END memcache_example_3]
+ }
+
+ @Test
+ public void testInsert1() {
+ doTest();
+ }
+
+ @Test
+ public void testInsert2() {
+ doTest();
+ }
+}
diff --git a/unittests/src/test/java/com/google/appengine/samples/unittest/MyFirstTest.java b/unittests/src/test/java/com/google/appengine/samples/unittest/MyFirstTest.java
new file mode 100644
index 00000000000..9e3273d51ba
--- /dev/null
+++ b/unittests/src/test/java/com/google/appengine/samples/unittest/MyFirstTest.java
@@ -0,0 +1,14 @@
+package com.google.appengine.samples.unittest;
+
+// [START framework]
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class MyFirstTest {
+ @Test
+ public void testAddition() {
+ assertEquals(4, 2 + 2);
+ }
+}
+// [END framework]
\ No newline at end of file
diff --git a/unittests/src/test/java/com/google/appengine/samples/unittest/OAuthTest.java b/unittests/src/test/java/com/google/appengine/samples/unittest/OAuthTest.java
new file mode 100644
index 00000000000..efc9c58397d
--- /dev/null
+++ b/unittests/src/test/java/com/google/appengine/samples/unittest/OAuthTest.java
@@ -0,0 +1,50 @@
+package com.google.appengine.samples.unittest;
+
+// [START oauth]
+import com.google.appengine.api.oauth.OAuthRequestException;
+import com.google.appengine.api.oauth.OAuthService;
+import com.google.appengine.api.oauth.OAuthServiceFactory;
+import com.google.appengine.api.users.User;
+import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
+import com.google.appengine.tools.development.testing.LocalUserServiceTestConfig;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class OAuthTest {
+ private static final String OAUTH_CONSUMER_KEY = "notexample.com";
+ private static final String OAUTH_EMAIL = "bozo@clown.com";
+ private static final String OAUTH_USER_ID = "bozo";
+ private static final String OAUTH_AUTH_DOMAIN = "clown.com";
+ private static final boolean OAUTH_IS_ADMIN = true;
+
+ private final LocalServiceTestHelper helper =
+ new LocalServiceTestHelper(new LocalUserServiceTestConfig()
+ .setOAuthConsumerKey(OAUTH_CONSUMER_KEY)
+ .setOAuthEmail(OAUTH_EMAIL)
+ .setOAuthUserId(OAUTH_USER_ID)
+ .setOAuthAuthDomain(OAUTH_AUTH_DOMAIN)
+ .setOAuthIsAdmin(OAUTH_IS_ADMIN));
+
+ @Before
+ public void setUp() {
+ helper.setUp();
+ }
+
+ @After
+ public void tearDown() {
+ helper.tearDown();
+ }
+
+ @Test
+ public void testConfig() throws OAuthRequestException {
+ OAuthService oauthService = OAuthServiceFactory.getOAuthService();
+ assertEquals(OAUTH_CONSUMER_KEY, oauthService.getOAuthConsumerKey());
+ assertEquals(new User(OAUTH_EMAIL, OAUTH_AUTH_DOMAIN, OAUTH_USER_ID),
+ oauthService.getCurrentUser());
+ assertEquals(OAUTH_IS_ADMIN, oauthService.isUserAdmin());
+ }
+}
+// [END oauth]
diff --git a/unittests/src/test/java/com/google/appengine/samples/unittest/TaskQueueTest.java b/unittests/src/test/java/com/google/appengine/samples/unittest/TaskQueueTest.java
new file mode 100644
index 00000000000..2e109fc5677
--- /dev/null
+++ b/unittests/src/test/java/com/google/appengine/samples/unittest/TaskQueueTest.java
@@ -0,0 +1,57 @@
+package com.google.appengine.samples.unittest;
+
+// [START taskqueue_example_1]
+import com.google.appengine.api.taskqueue.QueueFactory;
+import com.google.appengine.api.taskqueue.TaskOptions;
+import com.google.appengine.api.taskqueue.dev.LocalTaskQueue;
+import com.google.appengine.api.taskqueue.dev.QueueStateInfo;
+import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
+import com.google.appengine.tools.development.testing.LocalTaskQueueTestConfig;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class TaskQueueTest {
+
+ private final LocalServiceTestHelper helper =
+ new LocalServiceTestHelper(new LocalTaskQueueTestConfig());
+
+ @Before
+ public void setUp() {
+ helper.setUp();
+ }
+
+ @After
+ public void tearDown() {
+ helper.tearDown();
+ }
+
+
+ // Run this test twice to demonstrate we're not leaking state across tests.
+ // If we _are_ leaking state across tests we'll get an exception on the
+ // second test because there will already be a task with the given name.
+ private void doTest() throws InterruptedException {
+ QueueFactory.getDefaultQueue().add(TaskOptions.Builder.withTaskName("task29"));
+ // give the task time to execute if tasks are actually enabled (which they
+ // aren't, but that's part of the test)
+ Thread.sleep(1000);
+ LocalTaskQueue ltq = LocalTaskQueueTestConfig.getLocalTaskQueue();
+ QueueStateInfo qsi = ltq.getQueueStateInfo().get(QueueFactory.getDefaultQueue()
+ .getQueueName());
+ assertEquals(1, qsi.getTaskInfo().size());
+ assertEquals("task29", qsi.getTaskInfo().get(0).getTaskName());
+ }
+
+ @Test
+ public void testTaskGetsScheduled1() throws InterruptedException {
+ doTest();
+ }
+
+ @Test
+ public void testTaskGetsScheduled2() throws InterruptedException {
+ doTest();
+ }
+}
+// [END taskqueue_example_1]