diff --git a/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/FirebaseChannel.java b/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/FirebaseChannel.java
index 7bebf9adcfa..a2903f4a6f3 100644
--- a/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/FirebaseChannel.java
+++ b/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/FirebaseChannel.java
@@ -31,6 +31,7 @@
 
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
@@ -46,16 +47,18 @@
  */
 public class FirebaseChannel {
   private static final String FIREBASE_SNIPPET_PATH = "WEB-INF/view/firebase_config.jspf";
+  static InputStream firebaseConfigStream = null;
   private static final Collection FIREBASE_SCOPES = Arrays.asList(
       "https://www.googleapis.com/auth/firebase.database",
       "https://www.googleapis.com/auth/userinfo.email"
   );
   private static final String IDENTITY_ENDPOINT =
       "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit";
-  static final HttpTransport HTTP_TRANSPORT = new UrlFetchTransport();
 
   private String firebaseDbUrl;
   private GoogleCredential credential;
+  // Keep this a package-private member variable, so that it can be mocked for unit tests
+  HttpTransport httpTransport;
 
   private static FirebaseChannel instance;
 
@@ -79,11 +82,17 @@ public static FirebaseChannel getInstance() {
    */
   private FirebaseChannel() {
     try {
+      // This variables exist primarily so it can be stubbed out in unit tests.
+      if (null == firebaseConfigStream) {
+        firebaseConfigStream = new FileInputStream(FIREBASE_SNIPPET_PATH);
+      }
+
       String firebaseSnippet = CharStreams.toString(new InputStreamReader(
-          new FileInputStream(FIREBASE_SNIPPET_PATH), StandardCharsets.UTF_8));
+          firebaseConfigStream, StandardCharsets.UTF_8));
       firebaseDbUrl = parseFirebaseUrl(firebaseSnippet);
 
       credential = GoogleCredential.getApplicationDefault().createScoped(FIREBASE_SCOPES);
+      httpTransport = UrlFetchTransport.getDefaultInstance();
     } catch (IOException e) {
       throw new RuntimeException(e);
     }
@@ -109,7 +118,7 @@ private static String parseFirebaseUrl(String firebaseSnippet) {
   public void sendFirebaseMessage(String channelKey, Game game)
       throws IOException {
     // Make requests auth'ed using Application Default Credentials
-    HttpRequestFactory requestFactory = HTTP_TRANSPORT.createRequestFactory(credential);
+    HttpRequestFactory requestFactory = httpTransport.createRequestFactory(credential);
     GenericUrl url = new GenericUrl(
         String.format("%s/channels/%s.json", firebaseDbUrl, channelKey));
     HttpResponse response = null;
diff --git a/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/TicTacToeServlet.java b/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/TicTacToeServlet.java
index 1f9581d9b2e..8eeffda4451 100644
--- a/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/TicTacToeServlet.java
+++ b/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/TicTacToeServlet.java
@@ -73,7 +73,11 @@ public void doGet(HttpServletRequest request, HttpServletResponse response)
     Game game = null;
     String userId = userService.getCurrentUser().getUserId();
     if (gameKey != null) {
-      game = ofy.load().type(Game.class).id(gameKey).safe();
+      game = ofy.load().type(Game.class).id(gameKey).now();
+      if (null == game) {
+        response.sendError(HttpServletResponse.SC_NOT_FOUND);
+        return;
+      }
       if (game.getUserO() == null && !userId.equals(game.getUserX())) {
         game.setUserO(userId);
       }
@@ -102,6 +106,6 @@ public void doGet(HttpServletRequest request, HttpServletResponse response)
     request.setAttribute("channel_id", game.getChannelKey(userId));
     request.setAttribute("initial_message", new Gson().toJson(game));
     request.setAttribute("game_link", getGameUriWithGameParam(request, gameKey));
-    getServletContext().getRequestDispatcher("/WEB-INF/view/index.jsp").forward(request, response);
+    request.getRequestDispatcher("/WEB-INF/view/index.jsp").forward(request, response);
   }
 }
diff --git a/appengine/firebase-tictactoe/src/test/java/com/example/appengine/firetactoe/TicTacToeServletTest.java b/appengine/firebase-tictactoe/src/test/java/com/example/appengine/firetactoe/TicTacToeServletTest.java
new file mode 100644
index 00000000000..7666e06792d
--- /dev/null
+++ b/appengine/firebase-tictactoe/src/test/java/com/example/appengine/firetactoe/TicTacToeServletTest.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine.firetactoe;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.api.client.http.LowLevelHttpRequest;
+import com.google.api.client.http.LowLevelHttpResponse;
+import com.google.api.client.testing.http.MockHttpTransport;
+import com.google.api.client.testing.http.MockLowLevelHttpRequest;
+import com.google.api.client.testing.http.MockLowLevelHttpResponse;
+import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
+import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
+import com.google.appengine.tools.development.testing.LocalURLFetchServiceTestConfig;
+import com.google.appengine.tools.development.testing.LocalUserServiceTestConfig;
+import com.google.common.collect.ImmutableMap;
+import com.googlecode.objectify.Objectify;
+import com.googlecode.objectify.ObjectifyFactory;
+import com.googlecode.objectify.ObjectifyService;
+import com.googlecode.objectify.util.Closeable;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.StringBuffer;
+import java.util.HashMap;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Unit tests for {@link TicTacToeServlet}.
+ */
+@RunWith(JUnit4.class)
+public class TicTacToeServletTest {
+  private static final String USER_EMAIL = "whisky@tangofoxtr.ot";
+  private static final String USER_ID = "whiskytangofoxtrot";
+  private static final String FIREBASE_DB_URL = "http://firebase.com/dburl";
+
+  private final LocalServiceTestHelper helper =
+      new LocalServiceTestHelper(
+          // Set no eventual consistency, that way queries return all results.
+          // http://g.co/cloud/appengine/docs/java/tools/localunittesting#Java_Writing_High_Replication_Datastore_tests
+          new LocalDatastoreServiceTestConfig().setDefaultHighRepJobPolicyUnappliedJobPercentage(0),
+          new LocalUserServiceTestConfig(),
+          new LocalURLFetchServiceTestConfig()
+          )
+      .setEnvEmail(USER_EMAIL)
+      .setEnvAuthDomain("gmail.com")
+      .setEnvAttributes(new HashMap(
+          ImmutableMap.of("com.google.appengine.api.users.UserService.user_id_key", USER_ID)));
+
+  @Mock private HttpServletRequest mockRequest;
+  @Mock private HttpServletResponse mockResponse;
+  private StringWriter responseWriter;
+  protected Closeable dbSession;
+  @Mock RequestDispatcher requestDispatcher;
+
+  private TicTacToeServlet servletUnderTest;
+
+  @BeforeClass
+  public static void setUpBeforeClass() {
+    // Reset the Factory so that all translators work properly.
+    ObjectifyService.setFactory(new ObjectifyFactory());
+    ObjectifyService.register(Game.class);
+    // Mock out the firebase config
+    FirebaseChannel.firebaseConfigStream = new ByteArrayInputStream(
+        String.format("databaseURL: \"%s\"", FIREBASE_DB_URL).getBytes());
+  }
+
+  @Before
+  public void setUp() throws Exception {
+    MockitoAnnotations.initMocks(this);
+    helper.setUp();
+    dbSession = ObjectifyService.begin();
+
+    // Set up a fake HTTP response.
+    responseWriter = new StringWriter();
+    when(mockResponse.getWriter()).thenReturn(new PrintWriter(responseWriter));
+    when(mockRequest.getRequestURL()).thenReturn(new StringBuffer("https://timbre/"));
+    when(mockRequest.getRequestDispatcher("/WEB-INF/view/index.jsp")).thenReturn(requestDispatcher);
+
+    servletUnderTest = new TicTacToeServlet();
+  }
+
+  @After
+  public void tearDown() {
+    dbSession.close();
+    helper.tearDown();
+  }
+
+  @Test
+  public void doGet_loggedOut() throws Exception {
+    helper.setEnvIsLoggedIn(false);
+    servletUnderTest.doGet(mockRequest, mockResponse);
+
+    String response = responseWriter.toString();
+    assertThat(response).contains("sign in");
+  }
+
+  @Test
+  public void doGet_loggedIn_noGameKey() throws Exception {
+    helper.setEnvIsLoggedIn(true);
+    // Mock out the firebase response. See
+    // http://g.co/dv/api-client-library/java/google-http-java-client/unit-testing
+    MockHttpTransport mockHttpTransport = spy(new MockHttpTransport() {
+      @Override
+      public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
+        return new MockLowLevelHttpRequest() {
+          @Override
+          public LowLevelHttpResponse execute() throws IOException {
+            MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
+            response.setStatusCode(200);
+            return response;
+          }
+        };
+      }
+    });
+    FirebaseChannel.getInstance().httpTransport = mockHttpTransport;
+
+    servletUnderTest.doGet(mockRequest, mockResponse);
+
+    // Make sure the game object was created for a new game
+    Objectify ofy = ObjectifyService.ofy();
+    Game game = ofy.load().type(Game.class).first().safe();
+    assertThat(game.userX).isEqualTo(USER_ID);
+
+    verify(mockHttpTransport, times(1)).buildRequest(
+        eq("PATCH"), Matchers.matches(FIREBASE_DB_URL + "/channels/[\\w-]+.json$"));
+    verify(requestDispatcher).forward(mockRequest, mockResponse);
+    verify(mockRequest).setAttribute(eq("token"), anyString());
+    verify(mockRequest).setAttribute("game_key", game.id);
+    verify(mockRequest).setAttribute("me", USER_ID);
+    verify(mockRequest).setAttribute("channel_id", USER_ID + game.id);
+    verify(mockRequest).setAttribute(eq("initial_message"), anyString());
+    verify(mockRequest).setAttribute(eq("game_link"), anyString());
+  }
+
+  @Test
+  public void doGet_loggedIn_existingGame() throws Exception {
+    helper.setEnvIsLoggedIn(true);
+    // Mock out the firebase response. See
+    // http://g.co/dv/api-client-library/java/google-http-java-client/unit-testing
+    MockHttpTransport mockHttpTransport = spy(new MockHttpTransport() {
+      @Override
+      public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
+        return new MockLowLevelHttpRequest() {
+          @Override
+          public LowLevelHttpResponse execute() throws IOException {
+            MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
+            response.setStatusCode(200);
+            return response;
+          }
+        };
+      }
+    });
+    FirebaseChannel.getInstance().httpTransport = mockHttpTransport;
+
+    // Insert a game
+    Objectify ofy = ObjectifyService.ofy();
+    Game game = new Game("some-other-user-id", null, "         ", true);
+    ofy.save().entity(game).now();
+    String gameKey = game.getId();
+
+    when(mockRequest.getParameter("gameKey")).thenReturn(gameKey);
+
+    servletUnderTest.doGet(mockRequest, mockResponse);
+
+    // Make sure the game object was updated with the other player
+    game = ofy.load().type(Game.class).first().safe();
+    assertThat(game.userX).isEqualTo("some-other-user-id");
+    assertThat(game.userO).isEqualTo(USER_ID);
+
+    verify(mockHttpTransport, times(2)).buildRequest(
+        eq("PATCH"), Matchers.matches(FIREBASE_DB_URL + "/channels/[\\w-]+.json$"));
+    verify(requestDispatcher).forward(mockRequest, mockResponse);
+    verify(mockRequest).setAttribute(eq("token"), anyString());
+    verify(mockRequest).setAttribute("game_key", game.id);
+    verify(mockRequest).setAttribute("me", USER_ID);
+    verify(mockRequest).setAttribute("channel_id", USER_ID + gameKey);
+    verify(mockRequest).setAttribute(eq("initial_message"), anyString());
+    verify(mockRequest).setAttribute(eq("game_link"), anyString());
+  }
+
+  @Test
+  public void doGet_loggedIn_nonExistentGame() throws Exception {
+    helper.setEnvIsLoggedIn(true);
+
+    when(mockRequest.getParameter("gameKey")).thenReturn("does-not-exist");
+
+    servletUnderTest.doGet(mockRequest, mockResponse);
+
+    verify(mockResponse).sendError(404);
+  }
+}
diff --git a/unittests/pom.xml b/unittests/pom.xml
index 7b59f008c5d..37ef803e780 100644
--- a/unittests/pom.xml
+++ b/unittests/pom.xml
@@ -22,6 +22,7 @@
         UTF-8
         3.0.0
         2.5.1
+        1.22.0
     
 
     
@@ -66,6 +67,12 @@
             ${appengine.sdk.version}
             test
         
+        
+          com.google.api-client
+          google-api-client-appengine
+          ${google-api-client.version}
+          test
+        
     
 
     
diff --git a/unittests/src/test/java/com/google/appengine/samples/LocalUrlFetchTest.java b/unittests/src/test/java/com/google/appengine/samples/LocalUrlFetchTest.java
new file mode 100644
index 00000000000..a2f1eba6c67
--- /dev/null
+++ b/unittests/src/test/java/com/google/appengine/samples/LocalUrlFetchTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.appengine.samples;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.api.client.http.GenericUrl;
+import com.google.api.client.http.HttpRequestFactory;
+import com.google.api.client.http.HttpResponse;
+import com.google.api.client.http.LowLevelHttpRequest;
+import com.google.api.client.http.LowLevelHttpResponse;
+import com.google.api.client.testing.http.MockHttpTransport;
+import com.google.api.client.testing.http.MockLowLevelHttpRequest;
+import com.google.api.client.testing.http.MockLowLevelHttpResponse;
+import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
+import com.google.appengine.tools.development.testing.LocalURLFetchServiceTestConfig;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class LocalUrlFetchTest {
+  private final LocalServiceTestHelper helper =
+      new LocalServiceTestHelper(new LocalURLFetchServiceTestConfig());
+
+  @Before
+  public void setUp() {
+    helper.setUp();
+  }
+
+  @After
+  public void tearDown() {
+    helper.tearDown();
+  }
+
+  @Test
+  public void testMockUrlFetch() throws IOException {
+    // See http://g.co/dv/api-client-library/java/google-http-java-client/unit-testing
+    MockHttpTransport mockHttpTransport = new MockHttpTransport() {
+      @Override
+      public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
+        assertEquals(method, "GET");
+        assertEquals(url, "http://foo.bar/");
+
+        return new MockLowLevelHttpRequest() {
+          @Override
+          public LowLevelHttpResponse execute() throws IOException {
+            MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
+            response.setStatusCode(234);
+            return response;
+          }
+        };
+      }
+    };
+
+    HttpRequestFactory requestFactory = mockHttpTransport.createRequestFactory();
+    HttpResponse response = requestFactory.buildGetRequest(new GenericUrl("http://foo.bar/"))
+        .execute();
+    assertEquals(response.getStatusCode(), 234);
+  }
+}