GEODE:6784 Added unit tests to classes in the tomcat module (#4061)

Co-authored-by: Benjamin Ross <bross@pivotal.io>
Co-authored-by: Donal Evans <doevans@pivotal.io>
diff --git a/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/AbstractSessionCacheJUnitTest.java b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/AbstractSessionCacheJUnitTest.java
new file mode 100644
index 0000000..04cc183
--- /dev/null
+++ b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/AbstractSessionCacheJUnitTest.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.modules.session.catalina;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+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 javax.servlet.http.HttpSession;
+
+import org.apache.juli.logging.Log;
+import org.junit.Test;
+
+import org.apache.geode.cache.CustomExpiry;
+import org.apache.geode.cache.EntryNotFoundException;
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.RegionShortcut;
+import org.apache.geode.cache.execute.Execution;
+import org.apache.geode.distributed.DistributedSystem;
+import org.apache.geode.modules.util.RegionConfiguration;
+
+public abstract class AbstractSessionCacheJUnitTest {
+
+  protected String sessionRegionName = "sessionRegion";
+  private String sessionRegionAttributesId = RegionShortcut.PARTITION.toString();
+  private int nonDefaultMaxInactiveInterval = RegionConfiguration.DEFAULT_MAX_INACTIVE_INTERVAL + 1;
+  private boolean gatewayDeltaReplicationEnabled = true;
+  private boolean gatewayReplicationEnabled = true;
+  private boolean enableDebugListener = true;
+
+
+  protected SessionManager sessionManager = mock(SessionManager.class);
+  @SuppressWarnings("unchecked")
+  protected Region<String, HttpSession> sessionRegion = mock(Region.class);
+  protected DistributedSystem distributedSystem = mock(DistributedSystem.class);
+  protected Log logger = mock(Log.class);
+  protected Execution emptyExecution = mock(Execution.class);
+
+  protected AbstractSessionCache sessionCache;
+
+  @Test
+  public void createRegionConfigurationSetsAppropriateValuesWithDefaultMaxInactiveInterval() {
+    RegionConfiguration config = spy(new RegionConfiguration());
+    doReturn(config).when(sessionCache).getNewRegionConfiguration();
+
+    when(sessionManager.getRegionName()).thenReturn(sessionRegionName);
+    when(sessionManager.getRegionAttributesId()).thenReturn(sessionRegionAttributesId);
+    when(sessionManager.getMaxInactiveInterval())
+        .thenReturn(RegionConfiguration.DEFAULT_MAX_INACTIVE_INTERVAL);
+    when(sessionManager.getEnableGatewayDeltaReplication())
+        .thenReturn(gatewayDeltaReplicationEnabled);
+    when(sessionManager.getEnableGatewayReplication()).thenReturn(gatewayReplicationEnabled);
+    when(sessionManager.getEnableDebugListener()).thenReturn(enableDebugListener);
+
+    sessionCache.createRegionConfiguration();
+
+    verify(config).setRegionName(sessionRegionName);
+    verify(config).setRegionAttributesId(sessionRegionAttributesId);
+    verify(config, times(0)).setMaxInactiveInterval(anyInt());
+    verify(config, times(0)).setCustomExpiry(any(CustomExpiry.class));
+    verify(config).setEnableGatewayDeltaReplication(gatewayDeltaReplicationEnabled);
+    verify(config).setEnableGatewayReplication(gatewayReplicationEnabled);
+    verify(config).setEnableDebugListener(enableDebugListener);
+  }
+
+  @Test
+  public void createRegionConfigurationSetsAppropriateValuesWithNonDefaultMaxInactiveInterval() {
+    RegionConfiguration config = spy(new RegionConfiguration());
+    doReturn(config).when(sessionCache).getNewRegionConfiguration();
+
+    when(sessionManager.getRegionName()).thenReturn(sessionRegionName);
+    when(sessionManager.getRegionAttributesId()).thenReturn(sessionRegionAttributesId);
+    when(sessionManager.getMaxInactiveInterval()).thenReturn(nonDefaultMaxInactiveInterval);
+    when(sessionManager.getEnableGatewayDeltaReplication())
+        .thenReturn(gatewayDeltaReplicationEnabled);
+    when(sessionManager.getEnableGatewayReplication()).thenReturn(gatewayReplicationEnabled);
+    when(sessionManager.getEnableDebugListener()).thenReturn(enableDebugListener);
+
+    sessionCache.createRegionConfiguration();
+
+    verify(config).setRegionName(sessionRegionName);
+    verify(config).setRegionAttributesId(sessionRegionAttributesId);
+    verify(config).setMaxInactiveInterval(nonDefaultMaxInactiveInterval);
+    verify(config).setCustomExpiry(any(CustomExpiry.class));
+    verify(config).setEnableGatewayDeltaReplication(gatewayDeltaReplicationEnabled);
+    verify(config).setEnableGatewayReplication(gatewayReplicationEnabled);
+    verify(config).setEnableDebugListener(enableDebugListener);
+  }
+
+  @Test
+  public void destroySessionDoesNotThrowExceptionWhenGetOperatingRegionThrowsEntryNotFoundException() {
+    EntryNotFoundException exception = new EntryNotFoundException("Entry not found.");
+    String sessionId = "sessionId";
+    // For Client/Server the operating Region is always the session Region, for peer to peer this is
+    // only true when
+    // local caching is not enabled. For the purposes of this test the behavior is equivalent
+    // regardless of local
+    // caching.
+    when(sessionCache.getOperatingRegion()).thenReturn(sessionRegion);
+    doThrow(exception).when(sessionRegion).destroy(sessionId);
+
+    sessionCache.destroySession(sessionId);
+    verify(sessionCache).getOperatingRegion();
+  }
+}
diff --git a/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/ClientServerSessionCacheJUnitTest.java b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/ClientServerSessionCacheJUnitTest.java
new file mode 100644
index 0000000..2e34681
--- /dev/null
+++ b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/ClientServerSessionCacheJUnitTest.java
@@ -0,0 +1,318 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.modules.session.catalina;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+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 java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.servlet.http.HttpSession;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import org.apache.geode.Statistics;
+import org.apache.geode.cache.AttributesMutator;
+import org.apache.geode.cache.CacheListener;
+import org.apache.geode.cache.DataPolicy;
+import org.apache.geode.cache.InterestResultPolicy;
+import org.apache.geode.cache.RegionAttributes;
+import org.apache.geode.cache.RegionShortcut;
+import org.apache.geode.cache.client.ClientCache;
+import org.apache.geode.cache.client.ClientRegionFactory;
+import org.apache.geode.cache.client.ClientRegionShortcut;
+import org.apache.geode.cache.client.internal.InternalClientCache;
+import org.apache.geode.cache.client.internal.PoolImpl;
+import org.apache.geode.cache.execute.Function;
+import org.apache.geode.cache.execute.FunctionException;
+import org.apache.geode.cache.execute.ResultCollector;
+import org.apache.geode.internal.cache.GemFireCacheImpl;
+import org.apache.geode.modules.session.catalina.callback.SessionExpirationCacheListener;
+import org.apache.geode.modules.util.BootstrappingFunction;
+import org.apache.geode.modules.util.CreateRegionFunction;
+import org.apache.geode.modules.util.DebugCacheListener;
+import org.apache.geode.modules.util.RegionConfiguration;
+import org.apache.geode.modules.util.RegionStatus;
+import org.apache.geode.modules.util.SessionCustomExpiry;
+import org.apache.geode.modules.util.TouchPartitionedRegionEntriesFunction;
+import org.apache.geode.modules.util.TouchReplicatedRegionEntriesFunction;
+
+public class ClientServerSessionCacheJUnitTest extends AbstractSessionCacheJUnitTest {
+
+  private List<RegionStatus> regionStatusResultList = new ArrayList<>();
+  private ClientCache cache = mock(GemFireCacheImpl.class);
+  private ResultCollector collector = mock(ResultCollector.class);
+  private Statistics stats = mock(Statistics.class);
+  @SuppressWarnings("unchecked")
+  private ClientRegionFactory<String, HttpSession> regionFactory = mock(ClientRegionFactory.class);
+  @SuppressWarnings("unchecked")
+  private RegionAttributes<String, HttpSession> attributes = mock(RegionAttributes.class);
+
+  @Before
+  public void setUp() {
+    sessionCache = spy(new ClientServerSessionCache(sessionManager, cache));
+    doReturn(emptyExecution).when((ClientServerSessionCache) sessionCache)
+        .getExecutionForFunctionOnServers();
+    doReturn(emptyExecution).when((ClientServerSessionCache) sessionCache)
+        .getExecutionForFunctionOnServersWithArguments(any());
+    doReturn(emptyExecution).when((ClientServerSessionCache) sessionCache)
+        .getExecutionForFunctionOnServerWithRegionConfiguration(any());
+    doReturn(emptyExecution).when((ClientServerSessionCache) sessionCache)
+        .getExecutionForFunctionOnRegionWithFilter(any());
+
+    when(sessionManager.getLogger()).thenReturn(logger);
+    when(sessionManager.getEnableLocalCache()).thenReturn(true);
+    when(sessionManager.getRegionName()).thenReturn(sessionRegionName);
+    when(sessionManager.getMaxInactiveInterval())
+        .thenReturn(RegionConfiguration.DEFAULT_MAX_INACTIVE_INTERVAL);
+
+    when(cache.getDistributedSystem()).thenReturn(distributedSystem);
+    doReturn(regionFactory).when(cache)
+        .createClientRegionFactory(ClientRegionShortcut.CACHING_PROXY_HEAP_LRU);
+    when(((InternalClientCache) cache).isClient()).thenReturn(true);
+
+    when(emptyExecution.execute(any(Function.class))).thenReturn(collector);
+    when(emptyExecution.execute(any(String.class))).thenReturn(collector);
+
+    when(collector.getResult()).thenReturn(regionStatusResultList);
+
+    when(distributedSystem.createAtomicStatistics(any(), any())).thenReturn(stats);
+
+    regionStatusResultList.clear();
+    regionStatusResultList.add(RegionStatus.VALID);
+  }
+
+  @Test
+  public void initializeSessionCacheSucceeds() {
+    sessionCache.initialize();
+
+    verify(emptyExecution).execute(any(BootstrappingFunction.class));
+    verify(emptyExecution).execute(CreateRegionFunction.ID);
+    verify(cache).createClientRegionFactory(ClientRegionShortcut.CACHING_PROXY_HEAP_LRU);
+    verify(regionFactory, times(0)).setStatisticsEnabled(true);
+    verify(regionFactory, times(0)).setCustomEntryIdleTimeout(any(SessionCustomExpiry.class));
+    verify(regionFactory, times(0)).addCacheListener(any(SessionExpirationCacheListener.class));
+    verify(regionFactory).create(sessionRegionName);
+  }
+
+  @Test
+  public void bootstrappingFunctionThrowsException() {
+    FunctionException exception = new FunctionException();
+
+    ResultCollector exceptionCollector = mock(ResultCollector.class);
+
+    when(emptyExecution.execute(new BootstrappingFunction())).thenReturn(exceptionCollector);
+    when(exceptionCollector.getResult()).thenThrow(exception);
+
+    sessionCache.initialize();
+
+    verify(logger).warn("Caught unexpected exception:", exception);
+  }
+
+
+  @Test
+  public void createOrRetrieveRegionThrowsException() {
+    RuntimeException exception = new RuntimeException();
+    doThrow(exception).when((ClientServerSessionCache) sessionCache).createLocalSessionRegion();
+
+    assertThatThrownBy(() -> sessionCache.initialize()).hasCause(exception)
+        .isInstanceOf(IllegalStateException.class);
+
+    verify(logger).fatal("Unable to create or retrieve region", exception);
+
+  }
+
+  @Test
+  public void createRegionFunctionFailsOnServer() {
+    ArgumentCaptor<String> stringCaptor = ArgumentCaptor.forClass(String.class);
+
+    regionStatusResultList.clear();
+    regionStatusResultList.add(RegionStatus.INVALID);
+
+    assertThatThrownBy(() -> sessionCache.initialize()).isInstanceOf(IllegalStateException.class)
+        .hasCauseInstanceOf(IllegalStateException.class).hasMessageContaining(
+            "An exception occurred on the server while attempting to create or validate region named "
+                + sessionRegionName
+                + ". See the server log for additional details.");
+
+    verify(logger).fatal(stringCaptor.capture(), any(Exception.class));
+    assertThat(stringCaptor.getValue()).isEqualTo("Unable to create or retrieve region");
+  }
+
+  @Test
+  public void nonDefaultMaxTimeoutIntervalSetsExpirationDetails() {
+    // Setting the mocked return value of getMaxInactiveInterval to something distinctly not equal
+    // to the default
+    when(sessionManager.getMaxInactiveInterval())
+        .thenReturn(RegionConfiguration.DEFAULT_MAX_INACTIVE_INTERVAL + 1);
+
+    sessionCache.initialize();
+
+    verify(regionFactory).setStatisticsEnabled(true);
+    verify(regionFactory).setCustomEntryIdleTimeout(any(SessionCustomExpiry.class));
+    verify(regionFactory).addCacheListener(any(SessionExpirationCacheListener.class));
+  }
+
+  @Test
+  public void createLocalSessionRegionWithoutEnableLocalCache() {
+    when(sessionManager.getEnableLocalCache()).thenReturn(false);
+    doReturn(regionFactory).when(cache).createClientRegionFactory(ClientRegionShortcut.PROXY);
+    when(regionFactory.create(sessionRegionName)).thenReturn(sessionRegion);
+
+    sessionCache.initialize();
+
+    verify(regionFactory).addCacheListener(any(SessionExpirationCacheListener.class));
+    verify(sessionRegion).registerInterest("ALL_KEYS", InterestResultPolicy.KEYS);
+  }
+
+  @Test
+  public void createOrRetrieveRegionWithNonNullSessionRegionDoesNotCreateRegion() {
+    @SuppressWarnings("unchecked")
+    CacheListener<String, HttpSession>[] cacheListeners =
+        new CacheListener[] {new SessionExpirationCacheListener()};
+    doReturn(sessionRegion).when(cache).getRegion(sessionRegionName);
+    doReturn(attributes).when(sessionRegion).getAttributes();
+    doReturn(cacheListeners).when(attributes).getCacheListeners();
+
+    sessionCache.initialize();
+
+    verify((ClientServerSessionCache) sessionCache, times(0)).createSessionRegionOnServers();
+    verify((ClientServerSessionCache) sessionCache, times(0)).createLocalSessionRegion();
+  }
+
+  @Test
+  public void createOrRetrieveRegionWithNonNullSessionRegionAndNoSessionExpirationCacheListenerCreatesListener() {
+    @SuppressWarnings("unchecked")
+    CacheListener<String, HttpSession>[] cacheListeners =
+        new CacheListener[] {new DebugCacheListener()};
+    @SuppressWarnings("unchecked")
+    AttributesMutator<String, HttpSession> attributesMutator = mock(AttributesMutator.class);
+    doReturn(sessionRegion).when(cache).getRegion(sessionRegionName);
+    doReturn(attributes).when(sessionRegion).getAttributes();
+    doReturn(cacheListeners).when(attributes).getCacheListeners();
+    doReturn(attributesMutator).when(sessionRegion).getAttributesMutator();
+
+    sessionCache.initialize();
+
+    verify(attributesMutator).addCacheListener(any(SessionExpirationCacheListener.class));
+  }
+
+  @Test
+  public void createOrRetrieveRegionWithNonNullSessionProxyRegionRegistersInterestForAllKeys() {
+    @SuppressWarnings("unchecked")
+    CacheListener<String, HttpSession>[] cacheListeners =
+        new CacheListener[] {new SessionExpirationCacheListener()};
+    doReturn(sessionRegion).when(cache).getRegion(sessionRegionName);
+    doReturn(attributes).when(sessionRegion).getAttributes();
+    doReturn(cacheListeners).when(attributes).getCacheListeners();
+    when(attributes.getDataPolicy()).thenReturn(DataPolicy.EMPTY);
+
+    sessionCache.initialize();
+
+    verify(sessionRegion).registerInterest("ALL_KEYS", InterestResultPolicy.KEYS);
+  }
+
+  @Test
+  public void touchSessionsInvokesPRFunctionForPRAndDoesNotThrowExceptionWhenFunctionDoesNotThrowException() {
+    Set<String> sessionIds = new HashSet<>();
+
+    when(sessionManager.getRegionAttributesId()).thenReturn(RegionShortcut.PARTITION.toString());
+
+    sessionCache.touchSessions(sessionIds);
+
+    verify(emptyExecution).execute(TouchPartitionedRegionEntriesFunction.ID);
+  }
+
+  @Test
+  public void touchSessionsInvokesPRFunctionForPRAndThrowsExceptionWhenFunctionThrowsException() {
+    Set<String> sessionIds = new HashSet<>();
+    FunctionException exception = new FunctionException();
+    ResultCollector exceptionCollector = mock(ResultCollector.class);
+
+    when(sessionManager.getRegionAttributesId()).thenReturn(RegionShortcut.PARTITION.toString());
+    when(emptyExecution.execute(TouchPartitionedRegionEntriesFunction.ID))
+        .thenReturn(exceptionCollector);
+    when(exceptionCollector.getResult()).thenThrow(exception);
+
+    sessionCache.touchSessions(sessionIds);
+    verify(logger).warn("Caught unexpected exception:", exception);
+  }
+
+  @Test
+  public void touchSessionsInvokesRRFunctionForRRAndDoesNotThrowExceptionWhenFunctionDoesNotThrowException() {
+    // Need to invoke this to set the session region
+    when(regionFactory.create(sessionRegionName)).thenReturn(sessionRegion);
+    sessionCache.initialize();
+
+    Set<String> sessionIds = new HashSet<>();
+
+    when(sessionRegion.getFullPath()).thenReturn("/" + sessionRegionName);
+    when(sessionManager.getRegionAttributesId()).thenReturn(RegionShortcut.REPLICATE.toString());
+
+    sessionCache.touchSessions(sessionIds);
+    verify(emptyExecution).execute(TouchReplicatedRegionEntriesFunction.ID);
+  }
+
+  @Test
+  public void touchSessionsInvokesRRFunctionForRRAndThrowsExceptionWhenFunctionThrowsException() {
+    // Need to invoke this to set the session region
+    when(regionFactory.create(sessionRegionName)).thenReturn(sessionRegion);
+    sessionCache.initialize();
+
+    Set<String> sessionIds = new HashSet<>();
+    FunctionException exception = new FunctionException();
+    ResultCollector exceptionCollector = mock(ResultCollector.class);
+
+    when(sessionRegion.getFullPath()).thenReturn("/" + sessionRegionName);
+    when(sessionManager.getRegionAttributesId()).thenReturn(RegionShortcut.REPLICATE.toString());
+    when(emptyExecution.execute(TouchReplicatedRegionEntriesFunction.ID))
+        .thenReturn(exceptionCollector);
+    when(exceptionCollector.getResult()).thenThrow(exception);
+
+    sessionCache.touchSessions(sessionIds);
+    verify(logger).warn("Caught unexpected exception:", exception);
+  }
+
+  @Test
+  public void isBackingCacheEnabledReturnsTrueWhenCommitValveFailfastDisabled() {
+    assertThat(sessionCache.isBackingCacheAvailable()).isTrue();
+  }
+
+  @Test
+  public void isBackingCacheEnabledReturnsValueWhenCommitValveFailfastEnabled() {
+    boolean backingCacheEnabled = false;
+    PoolImpl pool = mock(PoolImpl.class);
+
+    when(sessionManager.isCommitValveFailfastEnabled()).thenReturn(true);
+    doReturn(pool).when((ClientServerSessionCache) sessionCache).findPoolInPoolManager();
+    when(pool.isPrimaryUpdaterAlive()).thenReturn(backingCacheEnabled);
+
+    assertThat(sessionCache.isBackingCacheAvailable()).isEqualTo(backingCacheEnabled);
+  }
+}
diff --git a/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/DeltaSessionFacadeJUnitTest.java b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/DeltaSessionFacadeJUnitTest.java
new file mode 100644
index 0000000..81254c7
--- /dev/null
+++ b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/DeltaSessionFacadeJUnitTest.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.modules.session.catalina;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Test;
+
+public class DeltaSessionFacadeJUnitTest {
+
+  @Test
+  public void DeltaSessionFacadeMakesProperCallsOnSessionWhenInvoked() {
+    DeltaSessionInterface session = spy(new DeltaSession());
+
+    DeltaSessionFacade facade = new DeltaSessionFacade(session);
+
+    doNothing().when(session).commit();
+    doReturn(true).when(session).isValid();
+
+    facade.commit();
+    facade.isValid();
+
+    verify(session).commit();
+    verify(session).isValid();
+  }
+}
diff --git a/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/DeltaSessionJUnitTest.java b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/DeltaSessionJUnitTest.java
new file mode 100644
index 0000000..1d55ce0
--- /dev/null
+++ b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/DeltaSessionJUnitTest.java
@@ -0,0 +1,205 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.modules.session.catalina;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+
+import javax.servlet.http.HttpSession;
+
+import org.apache.catalina.Manager;
+import org.apache.catalina.session.StandardSession;
+import org.apache.juli.logging.Log;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.geode.cache.Region;
+import org.apache.geode.internal.util.BlobHelper;
+import org.apache.geode.modules.session.catalina.internal.DeltaSessionAttributeEvent;
+import org.apache.geode.modules.session.catalina.internal.DeltaSessionStatistics;
+
+public class DeltaSessionJUnitTest {
+
+  private DeltaSessionManager manager = mock(DeltaSessionManager.class);
+  private Region<String, HttpSession> sessionRegion = mock(Region.class);
+  private SessionCache sessionCache = mock(ClientServerSessionCache.class);
+  DeltaSessionStatistics stats = mock(DeltaSessionStatistics.class);
+  private final String sessionRegionName = "sessionRegionName";
+  private final String contextName = "contextName";
+  private Log logger = mock(Log.class);
+
+  @Before
+  public void setup() {
+    when(manager.getRegionName()).thenReturn(sessionRegionName);
+    when(manager.getSessionCache()).thenReturn(sessionCache);
+    when(manager.getLogger()).thenReturn(logger);
+    when(manager.getContextName()).thenReturn(contextName);
+    when(manager.getStatistics()).thenReturn(stats);
+    // For Client/Server behavior and some PeerToPeer use cases the session region and operating
+    // regions
+    // will be the same.
+    when(sessionCache.getOperatingRegion()).thenReturn(sessionRegion);
+    when(logger.isDebugEnabled()).thenReturn(true);
+  }
+
+  @Test
+  public void sessionConstructionThrowsIllegalArgumentExceptionIfProvidedManagerIsNotDeltaSessionManager() {
+    Manager invalidManager = mock(Manager.class);
+
+    assertThatThrownBy(() -> new DeltaSession(invalidManager))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageContaining("The Manager must be an AbstractManager");
+  }
+
+  @Test
+  public void sessionConstructionDoesNotThrowExceptionWithValidArgument() {
+    DeltaSession session = new DeltaSession(manager);
+
+    verify(logger).debug(anyString());
+  }
+
+  @Test
+  public void getSessionCreatesFacadeWhenFacadeIsNullAndPackageProtectionDisabled() {
+    DeltaSession session = new DeltaSession(manager);
+
+    HttpSession returnedSession = session.getSession();
+
+    assertThat(returnedSession).isNotNull();
+  }
+
+  @Test
+  public void getSessionCreatesFacadeWhenFacadeIsNullAndPackageProtectionEnabled() {
+    DeltaSession session = spy(new DeltaSession(manager));
+    DeltaSessionFacade facade = mock(DeltaSessionFacade.class);
+    doReturn(true).when(session).isPackageProtectionEnabled();
+    doReturn(facade).when(session).getNewFacade(any(DeltaSession.class));
+
+    HttpSession returnedSession = session.getSession();
+
+    assertThat(returnedSession).isEqualTo(facade);
+  }
+
+  @Test
+  public void processExpiredIncrementsStatisticsCountForExpiredSessions() {
+    DeltaSession session = spy(new DeltaSession(manager));
+
+    doNothing().when((StandardSession) session).expire(false);
+    session.processExpired();
+
+    verify(stats).incSessionsExpired();
+  }
+
+  @Test
+  public void applyEventsAppliesEachEventAndPutsSessionIntoRegion() {
+    DeltaSessionAttributeEvent event1 = mock(DeltaSessionAttributeEvent.class);
+    DeltaSessionAttributeEvent event2 = mock(DeltaSessionAttributeEvent.class);
+    List<DeltaSessionAttributeEvent> events = new ArrayList<>();
+    events.add(event1);
+    events.add(event2);
+    Region<String, DeltaSessionInterface> region = mock(Region.class);
+    DeltaSession session = spy(new DeltaSession(manager));
+
+    session.applyAttributeEvents(region, events);
+
+    // confirm that events were all added to the queue
+    verify(session).addEventToEventQueue(event1);
+    verify(session).addEventToEventQueue(event2);
+
+    // confirm that session was put into region
+    verify(region).put(session.getId(), session, true);
+  }
+
+  @Test
+  public void commitThrowsIllegalStateExceptionWhenCalledOnInvalidSession() {
+    DeltaSession session = spy(new DeltaSession(manager));
+    String sessionId = "invalidatedSession";
+    doReturn(sessionId).when(session).getId();
+
+    assertThatThrownBy(() -> session.commit()).isInstanceOf(IllegalStateException.class)
+        .hasMessage("commit: Session " + sessionId + " already invalidated");
+  }
+
+  @Test
+  public void getSizeInBytesReturnsProperValueForMultipleAttributes() {
+    String attrName1 = "attrName1";
+    String attrName2 = "attrName2";
+    List attrList = new ArrayList<String>();
+    attrList.add(attrName1);
+    attrList.add(attrName2);
+
+    Enumeration<String> attrNames = Collections.enumeration(attrList);
+
+    byte[] value1 = {0, 0, 0, 0};
+    byte[] value2 = {0, 0, 0, 0, 0};
+    int totalSize = value1.length + value2.length;
+
+    DeltaSession session = spy(new DeltaSession(manager));
+    doReturn(attrNames).when(session).getAttributeNames();
+    doReturn(value1).when(session).getAttributeWithoutDeserialize(attrName1);
+    doReturn(value2).when(session).getAttributeWithoutDeserialize(attrName2);
+
+    int sessionSize = session.getSizeInBytes();
+
+    assertThat(sessionSize).isEqualTo(totalSize);
+  }
+
+  @Test
+  public void serializeLogsWarningWhenExceptionIsThrownDuringSerialization() throws IOException {
+    Object obj = "unserialized object";
+    String exceptionMessaage = "Serialization failed.";
+    IOException exception = new IOException(exceptionMessaage);
+
+    DeltaSession session = spy(new DeltaSession(manager));
+    doThrow(exception).when(session).serializeViaBlobHelper(obj);
+    session.serialize(obj);
+
+    verify(logger).warn(anyString(), any(IOException.class));
+  }
+
+  @Test
+  public void serializeReturnsSerializedObject() throws IOException {
+    Object obj = "unserialized object";
+    byte[] serializedObj = BlobHelper.serializeToBlob(obj);
+
+    DeltaSession session = spy(new DeltaSession(manager));
+    byte[] result = session.serialize(obj);
+
+    assertThat(result).isEqualTo(serializedObj);
+  }
+
+  // @Test
+  // public void testToData() throws IOException {
+  // DeltaSession session = spy(new DeltaSession(manager));
+  // DataOutput out = mock(DataOutput.class);
+  //
+  // session.toData(out);
+  // }
+}
diff --git a/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/DeltaSessionManagerJUnitTest.java b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/DeltaSessionManagerJUnitTest.java
new file mode 100644
index 0000000..fec8571
--- /dev/null
+++ b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/DeltaSessionManagerJUnitTest.java
@@ -0,0 +1,471 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.modules.session.catalina;
+
+import static org.apache.geode.modules.util.RegionConfiguration.DEFAULT_MAX_INACTIVE_INTERVAL;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
+
+import java.beans.PropertyChangeEvent;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.servlet.http.HttpSession;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Loader;
+import org.apache.catalina.Session;
+import org.apache.catalina.session.StandardSession;
+import org.apache.juli.logging.Log;
+import org.junit.Test;
+
+import org.apache.geode.cache.Cache;
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.query.FunctionDomainException;
+import org.apache.geode.cache.query.NameResolutionException;
+import org.apache.geode.cache.query.Query;
+import org.apache.geode.cache.query.QueryInvocationTargetException;
+import org.apache.geode.cache.query.SelectResults;
+import org.apache.geode.cache.query.TypeMismatchException;
+import org.apache.geode.cache.query.internal.InternalQueryService;
+import org.apache.geode.cache.query.internal.LinkedResultSet;
+import org.apache.geode.internal.cache.GemFireCacheImpl;
+import org.apache.geode.modules.session.catalina.internal.DeltaSessionStatistics;
+
+public abstract class DeltaSessionManagerJUnitTest {
+
+  protected DeltaSessionManager manager;
+  protected AbstractSessionCache sessionCache;
+  protected Cache cache;
+  protected Log logger;
+  protected Context context;
+  protected DeltaSessionStatistics managerStats;
+  protected DeltaSessionStatistics cacheStats;
+  protected Region<String, HttpSession> operatingRegion;
+
+  public void initTest() {
+    sessionCache = mock(AbstractSessionCache.class);
+    cache = mock(GemFireCacheImpl.class);
+    logger = mock(Log.class);
+    context = mock(Context.class);
+    managerStats = mock(DeltaSessionStatistics.class);
+    cacheStats = mock(DeltaSessionStatistics.class);
+    operatingRegion = mock(Region.class);
+
+    doReturn(sessionCache).when(manager).getSessionCache();
+    doReturn(logger).when(manager).getLogger();
+    doReturn(context).when(manager).getTheContext();
+    doReturn(managerStats).when(manager).getStatistics();
+    doReturn(cacheStats).when(sessionCache).getStatistics();
+    doReturn(operatingRegion).when(sessionCache).getOperatingRegion();
+  }
+
+  @Test
+  public void getRegionAttributesIdSetsIdFromSessionCacheWhenAttributesIdIsNull() {
+    String regionAttributesId = "attributesIdFromSessionCache";
+
+    doReturn(regionAttributesId).when(sessionCache).getDefaultRegionAttributesId();
+    String attrId = manager.getRegionAttributesId();
+
+    verify(sessionCache).getDefaultRegionAttributesId();
+    assertThat(attrId).isEqualTo(regionAttributesId);
+  }
+
+  @Test
+  public void getEnableLocalCacheSetsIdFromSessionCacheWhenEnableLocalCacheIsNull() {
+    boolean isLocalCacheEnabled = true;
+
+    doReturn(isLocalCacheEnabled).when(sessionCache).getDefaultEnableLocalCache();
+    Boolean localCacheEnabledValue = manager.getEnableLocalCache();
+
+    verify(sessionCache).getDefaultEnableLocalCache();
+    assertThat(localCacheEnabledValue).isEqualTo(isLocalCacheEnabled);
+  }
+
+  @Test
+  public void findSessionsReturnsNullWhenIdIsNull() throws IOException {
+    Session session = manager.findSession(null);
+
+    assertThat(session).isNull();
+  }
+
+  @Test
+  public void findSessionsReturnsNullAndLogsMessageWhenContextNameIsNotValid() throws IOException {
+    String sessionId = "sessionId";
+    String contextName = "contextName";
+    String invalidContextName = "invalidContextName";
+
+    DeltaSession expectedSession = mock(DeltaSession.class);
+    when(sessionCache.getSession(sessionId)).thenReturn(expectedSession);
+    when(expectedSession.getContextName()).thenReturn(invalidContextName);
+    when(context.getName()).thenReturn(contextName);
+
+    Session session = manager.findSession(sessionId);
+
+    verify(logger).info(anyString());
+    assertThat(session).isNull();
+  }
+
+  @Test
+  public void findSessionsReturnsNullWhenIdIsNotFound() throws IOException {
+    String sessionId = "sessionId";
+
+    when(sessionCache.getSession(sessionId)).thenReturn(null);
+
+    Session session = manager.findSession(sessionId);
+
+    assertThat(session).isNull();
+  }
+
+  @Test
+  public void findSessionsReturnsProperSessionByIdWhenIdAndContextNameIsValid() throws IOException {
+    String sessionId = "sessionId";
+    String contextName = "contextName";
+
+    DeltaSession expectedSession = mock(DeltaSession.class);
+    when(sessionCache.getSession(sessionId)).thenReturn(expectedSession);
+    when(expectedSession.getContextName()).thenReturn(contextName);
+    when(context.getName()).thenReturn(contextName);
+
+    Session session = manager.findSession(sessionId);
+
+    assertThat(session).isEqualTo(expectedSession);
+  }
+
+  @Test
+  public void removeProperlyDestroysSessionFromSessionCacheWhenSessionIsNotExpired() {
+    DeltaSession sessionToDestroy = mock(DeltaSession.class);
+    String sessionId = "sessionId";
+
+    when(sessionToDestroy.getId()).thenReturn(sessionId);
+    when(sessionToDestroy.getExpired()).thenReturn(false);
+
+    manager.remove(sessionToDestroy);
+
+    verify(sessionCache).destroySession(sessionId);
+  }
+
+  @Test
+  public void removeDoesNotDestroySessionFromSessionCacheWhenSessionIsExpired() {
+    DeltaSession sessionToDestroy = mock(DeltaSession.class);
+    String sessionId = "sessionId";
+
+    when(sessionToDestroy.getId()).thenReturn(sessionId);
+    when(sessionToDestroy.getExpired()).thenReturn(true);
+
+    manager.remove(sessionToDestroy);
+
+    verify(sessionCache, times(0)).destroySession(sessionId);
+  }
+
+  @Test
+  public void addPutsSessionIntoSessionCacheAndIncrementsStats() {
+    DeltaSession sessionToPut = mock(DeltaSession.class);
+
+    manager.add(sessionToPut);
+
+    verify(sessionCache).putSession(sessionToPut);
+    verify(cacheStats).incSessionsCreated();
+  }
+
+  @Test
+  public void listIdsListsAllPresentIds() {
+    Set<String> ids = new HashSet<>();
+    ids.add("id1");
+    ids.add("id2");
+    ids.add("id3");
+
+    when(sessionCache.keySet()).thenReturn(ids);
+
+    String listOutput = manager.listSessionIds();
+
+    for (String id : ids) {
+      assertThat(listOutput).contains(id);
+    }
+  }
+
+  @Test
+  public void loadActivatesAndAddsSingleSessionWithValidIdAndMoreRecentAccessTime()
+      throws IOException, ClassNotFoundException {
+    String contextPath = "contextPath";
+    String expectedStoreDir = "";
+    DeltaSession newSession = mock(DeltaSession.class);
+    DeltaSession existingSession = mock(DeltaSession.class);
+
+    prepareMocksForLoadTest(contextPath, newSession, existingSession, expectedStoreDir);
+
+    manager.load();
+
+    verify(newSession).activate();
+    verify(manager).add(newSession);
+  }
+
+  @Test
+  public void loadLogsWarningAndDoesNotAddSessionWhenSessionStoreNotFound()
+      throws IOException, ClassNotFoundException {
+    String contextPath = "contextPath";
+    String expectedStoreDir = "";
+    DeltaSession newSession = mock(DeltaSession.class);
+    DeltaSession existingSession = mock(DeltaSession.class);
+
+    prepareMocksForLoadTest(contextPath, newSession, existingSession, expectedStoreDir);
+
+    doReturn(null).when(manager).getFileAtPath(any(), any());
+
+    manager.load();
+
+    verify(logger).debug("No session store file found");
+    verify(manager, times(0)).add(any());
+  }
+
+  @Test
+  public void loadDoesNotAddSessionToManagerWithValidIdAndLessRecentAccessTime()
+      throws IOException, ClassNotFoundException {
+    String contextPath = "contextPath";
+    String expectedStoreDir = "";
+    DeltaSession newSession = mock(DeltaSession.class);
+    DeltaSession existingSession = mock(DeltaSession.class);
+
+    prepareMocksForLoadTest(contextPath, newSession, existingSession, expectedStoreDir);
+
+    when(existingSession.getLastAccessedTime()).thenReturn(2L);
+
+    manager.load();
+
+    verify(newSession, times(0)).activate();
+    verify(manager, times(0)).add(newSession);
+  }
+
+  @Test
+  public void unloadWritesSingleSessionToDiskWhenIdIsValid()
+      throws IOException, NameResolutionException, TypeMismatchException,
+      QueryInvocationTargetException, FunctionDomainException {
+    String sessionId = "sessionId";
+    DeltaSession session = mock(DeltaSession.class);
+    FileOutputStream fos = mock(FileOutputStream.class);
+    BufferedOutputStream bos = mock(BufferedOutputStream.class);
+    ObjectOutputStream oos = mock(ObjectOutputStream.class);
+
+    prepareMocksForUnloadTest(sessionId, fos, bos, oos, session);
+
+    manager.unload();
+
+    verify((StandardSession) session).writeObjectData(oos);
+  }
+
+  @Test
+  public void unloadDoesNotWriteSessionToDiskAndClosesOutputStreamsWhenOutputStreamThrowsIOException()
+      throws IOException, NameResolutionException, TypeMismatchException,
+      QueryInvocationTargetException, FunctionDomainException {
+    String sessionId = "sessionId";
+    DeltaSession session = mock(DeltaSession.class);
+    FileOutputStream fos = mock(FileOutputStream.class);
+    BufferedOutputStream bos = mock(BufferedOutputStream.class);
+    ObjectOutputStream oos = mock(ObjectOutputStream.class);
+
+    prepareMocksForUnloadTest(sessionId, fos, bos, oos, session);
+
+    String exceptionMessage = "Output Stream IOException";
+
+    IOException exception = new IOException(exceptionMessage);
+
+    doThrow(exception).when(manager).getObjectOutputStream(bos);
+
+    assertThatThrownBy(() -> manager.unload()).isInstanceOf(IOException.class)
+        .hasMessage(exceptionMessage);
+
+    verify((StandardSession) session, times(0)).writeObjectData(oos);
+    verify(bos).close();
+    verify(fos).close();
+  }
+
+  @Test
+  public void unloadDoesNotWriteSessionToDiskAndClosesOutputStreamsWhenSessionIsWrongClass()
+      throws IOException, NameResolutionException, TypeMismatchException,
+      QueryInvocationTargetException, FunctionDomainException {
+    String sessionId = "sessionId";
+    DeltaSession session = mock(DeltaSession.class);
+    FileOutputStream fos = mock(FileOutputStream.class);
+    BufferedOutputStream bos = mock(BufferedOutputStream.class);
+    ObjectOutputStream oos = mock(ObjectOutputStream.class);
+
+    prepareMocksForUnloadTest(sessionId, fos, bos, oos, session);
+
+    Session invalidSession =
+        mock(Session.class, withSettings().extraInterfaces(DeltaSessionInterface.class));
+
+    doReturn(invalidSession).when(manager).findSession(sessionId);
+
+    assertThatThrownBy(() -> manager.unload()).isInstanceOf(IOException.class);
+
+    verify((StandardSession) session, times(0)).writeObjectData(oos);
+    verify(oos).close();
+  }
+
+  @Test
+  public void successfulUnloadWithClientServerSessionCachePerformsLocalDestroy()
+      throws IOException, NameResolutionException, TypeMismatchException,
+      QueryInvocationTargetException, FunctionDomainException {
+    String sessionId = "sessionId";
+    DeltaSession session = mock(DeltaSession.class);
+    FileOutputStream fos = mock(FileOutputStream.class);
+    BufferedOutputStream bos = mock(BufferedOutputStream.class);
+    ObjectOutputStream oos = mock(ObjectOutputStream.class);
+
+    prepareMocksForUnloadTest(sessionId, fos, bos, oos, session);
+
+    when(sessionCache.isClientServer()).thenReturn(true);
+    when(session.getId()).thenReturn(sessionId);
+
+    manager.unload();
+
+    verify((StandardSession) session).writeObjectData(oos);
+    verify(operatingRegion).localDestroy(sessionId);
+  }
+
+  @Test
+  public void propertyChangeSetsMaxInactiveIntervalWithCorrectPropertyNameAndValue() {
+    String propertyName = "sessionTimeout";
+    PropertyChangeEvent event = mock(PropertyChangeEvent.class);
+    Context eventContext = mock(Context.class);
+    Integer newValue = 1;
+
+    when(event.getSource()).thenReturn(eventContext);
+    when(event.getPropertyName()).thenReturn(propertyName);
+    when(event.getNewValue()).thenReturn(newValue);
+
+    manager.propertyChange(event);
+
+    verify(manager).setMaxInactiveInterval(newValue * 60);
+  }
+
+  @Test
+  public void propertyChangeDoesNotSetMaxInactiveIntervalWithIncorrectPropertyName() {
+    String propertyName = "wrong name";
+    PropertyChangeEvent event = mock(PropertyChangeEvent.class);
+    Context eventContext = mock(Context.class);
+
+    when(event.getSource()).thenReturn(eventContext);
+    when(event.getPropertyName()).thenReturn(propertyName);
+
+    manager.propertyChange(event);
+
+    verify(manager, times(0)).setMaxInactiveInterval(anyInt());
+  }
+
+  @Test
+  public void propertyChangeDoesNotSetNewMaxInactiveIntervalWithCorrectPropertyNameAndInvalidPropertyValue() {
+    String propertyName = "sessionTimeout";
+    PropertyChangeEvent event = mock(PropertyChangeEvent.class);
+    Context eventContext = mock(Context.class);
+    Integer newValue = -2;
+    Integer oldValue = DEFAULT_MAX_INACTIVE_INTERVAL;
+
+    when(event.getSource()).thenReturn(eventContext);
+    when(event.getPropertyName()).thenReturn(propertyName);
+    when(event.getNewValue()).thenReturn(newValue);
+    when(event.getOldValue()).thenReturn(oldValue);
+
+    manager.propertyChange(event);
+
+    verify(manager).setMaxInactiveInterval(oldValue);
+  }
+
+  public void prepareMocksForUnloadTest(String sessionId, FileOutputStream fos,
+      BufferedOutputStream bos, ObjectOutputStream oos, DeltaSession session)
+      throws NameResolutionException, TypeMismatchException, QueryInvocationTargetException,
+      FunctionDomainException, IOException {
+    String regionName = "regionName";
+    String contextPath = "contextPath";
+    String catalinaBaseSystemProp = "Catalina/Base";
+    String systemFileSeparator = "/";
+    String expectedStoreDir = catalinaBaseSystemProp + systemFileSeparator + "temp";
+
+    InternalQueryService queryService = mock(InternalQueryService.class);
+    Query query = mock(Query.class);
+    File store = mock(File.class);
+    SelectResults results = new LinkedResultSet();
+
+    when(sessionCache.getCache()).thenReturn(cache);
+    when(context.getPath()).thenReturn(contextPath);
+    when(cache.getQueryService()).thenReturn(queryService);
+    when(queryService.newQuery(anyString())).thenReturn(query);
+    when(query.execute()).thenReturn(results);
+    doReturn(catalinaBaseSystemProp).when(manager)
+        .getSystemPropertyValue(DeltaSessionManager.catalinaBaseSystemProperty);
+    doReturn(systemFileSeparator).when(manager)
+        .getSystemPropertyValue(DeltaSessionManager.fileSeparatorSystemProperty);
+    doReturn(store).when(manager).getFileAtPath(expectedStoreDir, contextPath);
+    doReturn(fos).when(manager).getFileOutputStream(store);
+    doReturn(bos).when(manager).getBufferedOutputStream(fos);
+    doReturn(oos).when(manager).getObjectOutputStream(bos);
+    doReturn(regionName).when(manager).getRegionName();
+    doReturn(session).when(manager).findSession(sessionId);
+    doNothing().when(manager).writeToObjectOutputStream(any(), any());
+
+    results.add(sessionId);
+  }
+
+  public void prepareMocksForLoadTest(String contextPath, DeltaSession newSession,
+      DeltaSession existingSession, String expectedStoreDir)
+      throws IOException, ClassNotFoundException {
+    String catalinaBaseSystemProp = "Catalina/Base";
+    String systemFileSeparator = "/";
+    expectedStoreDir = catalinaBaseSystemProp + systemFileSeparator + "temp";
+    String newSessionId = "newSessionId";
+
+    File store = mock(File.class);
+    FileInputStream fis = mock(FileInputStream.class);
+    BufferedInputStream bis = mock(BufferedInputStream.class);
+    ObjectInputStream ois = mock(ObjectInputStream.class);
+    Loader loader = mock(Loader.class);
+
+    when(context.getPath()).thenReturn(contextPath);
+    when(context.getLoader()).thenReturn(loader);
+    when(newSession.getId()).thenReturn(newSessionId);
+    when(newSession.getLastAccessedTime()).thenReturn(1L);
+    when(newSession.isValid()).thenReturn(true);
+    when(existingSession.getLastAccessedTime()).thenReturn(0L);
+    doReturn(catalinaBaseSystemProp).when(manager).getSystemPropertyValue("catalina.base");
+    doReturn(systemFileSeparator).when(manager).getSystemPropertyValue("file.separator");
+    doReturn(store).when(manager).getFileAtPath(expectedStoreDir, contextPath);
+    doReturn(fis).when(manager).getFileInputStream(store);
+    doReturn(bis).when(manager).getBufferedInputStream(fis);
+    doReturn(ois).when(manager).getObjectInputStream(bis);
+    doReturn(1).when(manager).getSessionCountFromObjectInputStream(ois);
+    doReturn(newSession).when(manager).getNewSession();
+    doReturn(existingSession).when(operatingRegion).get(newSessionId);
+  }
+}
diff --git a/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/PeerToPeerSessionCacheJUnitTest.java b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/PeerToPeerSessionCacheJUnitTest.java
new file mode 100644
index 0000000..2bea8de
--- /dev/null
+++ b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/PeerToPeerSessionCacheJUnitTest.java
@@ -0,0 +1,204 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.modules.session.catalina;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+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 java.util.HashSet;
+import java.util.Set;
+
+import javax.servlet.http.HttpSession;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.geode.cache.Cache;
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.RegionFactory;
+import org.apache.geode.cache.RegionShortcut;
+import org.apache.geode.cache.execute.FunctionException;
+import org.apache.geode.cache.execute.ResultCollector;
+import org.apache.geode.modules.session.catalina.callback.SessionExpirationCacheListener;
+import org.apache.geode.modules.util.RegionConfiguration;
+import org.apache.geode.modules.util.SessionCustomExpiry;
+import org.apache.geode.modules.util.TouchPartitionedRegionEntriesFunction;
+import org.apache.geode.modules.util.TouchReplicatedRegionEntriesFunction;
+
+public class PeerToPeerSessionCacheJUnitTest extends AbstractSessionCacheJUnitTest {
+
+  private String localRegionName = sessionRegionName + "_local";
+  @SuppressWarnings("unchecked")
+  private RegionFactory<String, HttpSession> regionFactory = mock(RegionFactory.class);
+  @SuppressWarnings("unchecked")
+  private Region<String, HttpSession> localRegion = mock(Region.class);
+  private Cache cache = mock(Cache.class);
+
+  @Before
+  public void setUp() {
+    sessionCache = spy(new PeerToPeerSessionCache(sessionManager, cache));
+    doReturn(sessionRegion).when((PeerToPeerSessionCache) sessionCache)
+        .createRegionUsingHelper(any(RegionConfiguration.class));
+    doReturn(true).when((PeerToPeerSessionCache) sessionCache)
+        .isFunctionRegistered(any(String.class));
+
+    when(sessionManager.getRegionName()).thenReturn(sessionRegionName);
+    when(sessionManager.getRegionAttributesId()).thenReturn(RegionShortcut.PARTITION.toString());
+    when(sessionManager.getLogger()).thenReturn(logger);
+    when(sessionManager.getEnableLocalCache()).thenReturn(true);
+    when(sessionManager.getMaxInactiveInterval())
+        .thenReturn(RegionConfiguration.DEFAULT_MAX_INACTIVE_INTERVAL);
+
+    when(sessionRegion.getName()).thenReturn(sessionRegionName);
+
+    doReturn(regionFactory).when(cache).createRegionFactory(RegionShortcut.LOCAL_HEAP_LRU);
+    when(regionFactory.create(localRegionName)).thenReturn(localRegion);
+
+    when(cache.getDistributedSystem()).thenReturn(distributedSystem);
+  }
+
+  @Test
+  public void initializeSessionCacheSucceeds() {
+    sessionCache.initialize();
+
+    verify(cache).createRegionFactory(RegionShortcut.LOCAL_HEAP_LRU);
+    verify(regionFactory).create(localRegionName);
+    verify((PeerToPeerSessionCache) sessionCache)
+        .createRegionUsingHelper(any(RegionConfiguration.class));
+    verify(regionFactory, times(0)).setStatisticsEnabled(true);
+    verify(regionFactory, times(0)).setCustomEntryIdleTimeout(any(SessionCustomExpiry.class));
+    verify(regionFactory, times(0)).addCacheListener(any(SessionExpirationCacheListener.class));
+  }
+
+  @Test
+  public void functionRegistrationDoesNotThrowException() {
+    doReturn(false).when((PeerToPeerSessionCache) sessionCache)
+        .isFunctionRegistered(any(String.class));
+
+    sessionCache.initialize();
+
+    verify((PeerToPeerSessionCache) sessionCache).registerFunctionWithFunctionService(any(
+        TouchPartitionedRegionEntriesFunction.class));
+    verify((PeerToPeerSessionCache) sessionCache).registerFunctionWithFunctionService(any(
+        TouchReplicatedRegionEntriesFunction.class));
+  }
+
+  @Test
+  public void initializeSessionCacheSucceedsWhenSessionRegionAlreadyExists() {
+    doReturn(sessionRegion).when(cache).getRegion(sessionRegionName);
+    doNothing().when((PeerToPeerSessionCache) sessionCache)
+        .validateRegionUsingRegionhelper(any(RegionConfiguration.class), any(Region.class));
+
+    sessionCache.initialize();
+
+    verify((PeerToPeerSessionCache) sessionCache, times(0))
+        .createRegionUsingHelper(any(RegionConfiguration.class));
+  }
+
+  @Test
+  public void nonDefaultMaxTimeoutIntervalSetsExpirationDetails() {
+    // Setting the mocked return value of getMaxInactiveInterval to something distinctly not equal
+    // to the default
+    when(sessionManager.getMaxInactiveInterval())
+        .thenReturn(RegionConfiguration.DEFAULT_MAX_INACTIVE_INTERVAL + 1);
+
+    sessionCache.initialize();
+
+    verify(regionFactory).setStatisticsEnabled(true);
+    verify(regionFactory).setCustomEntryIdleTimeout(any(SessionCustomExpiry.class));
+    verify(regionFactory).addCacheListener(any(SessionExpirationCacheListener.class));
+  }
+
+  @Test
+  public void initializeSessionCacheSucceedsWhenLocalRegionAlreadyExists() {
+    doReturn(localRegion).when(cache).getRegion(localRegionName);
+
+    sessionCache.initialize();
+
+    verify(regionFactory, times(0)).create(any(String.class));
+  }
+
+
+  @Test
+  public void operationRegionIsCorrectlySetWhenEnableLocalCachingIsFalse() {
+    when(sessionManager.getEnableLocalCache()).thenReturn(false);
+
+    sessionCache.initialize();
+
+    verify(cache, times(0)).getRegion(localRegionName);
+    assertThat(sessionCache.sessionRegion).isEqualTo(sessionCache.operatingRegion);
+  }
+
+  @Test
+  public void touchSessionsWithPartitionedRegionSucceeds() {
+    Set<String> sessionIds = new HashSet<>();
+    ResultCollector collector = mock(ResultCollector.class);
+
+    when(sessionManager.getRegionAttributesId()).thenReturn(RegionShortcut.PARTITION.toString());
+    doReturn(emptyExecution).when((PeerToPeerSessionCache) sessionCache)
+        .getExecutionForFunctionOnRegionWithFilter(sessionIds);
+    when(emptyExecution.execute(TouchPartitionedRegionEntriesFunction.ID)).thenReturn(collector);
+
+    sessionCache.touchSessions(sessionIds);
+
+    verify(emptyExecution).execute(TouchPartitionedRegionEntriesFunction.ID);
+    verify(collector).getResult();
+  }
+
+  @Test
+  public void touchSessionsWithReplicatedRegionSucceeds() {
+    // Need to invoke this to set the session region
+    sessionCache.initialize();
+
+    Set<String> sessionIds = new HashSet<>();
+    ResultCollector collector = mock(ResultCollector.class);
+
+    when(sessionManager.getRegionAttributesId()).thenReturn(RegionShortcut.REPLICATE.toString());
+    doReturn(emptyExecution).when((PeerToPeerSessionCache) sessionCache)
+        .getExecutionForFunctionOnMembersWithArguments(any(Object[].class));
+    when(emptyExecution.execute(TouchReplicatedRegionEntriesFunction.ID)).thenReturn(collector);
+
+    sessionCache.touchSessions(sessionIds);
+
+    verify(emptyExecution).execute(TouchReplicatedRegionEntriesFunction.ID);
+    verify(collector).getResult();
+  }
+
+  @Test
+  public void touchSessionsCatchesThrownException() {
+    Set<String> sessionIds = new HashSet<>();
+    ResultCollector collector = mock(ResultCollector.class);
+    FunctionException exception = new FunctionException();
+
+    when(sessionManager.getRegionAttributesId()).thenReturn(RegionShortcut.PARTITION.toString());
+    doReturn(emptyExecution).when((PeerToPeerSessionCache) sessionCache)
+        .getExecutionForFunctionOnRegionWithFilter(sessionIds);
+    when(emptyExecution.execute(TouchPartitionedRegionEntriesFunction.ID)).thenReturn(collector);
+    doThrow(exception).when(collector).getResult();
+
+    sessionCache.touchSessions(sessionIds);
+
+    verify(logger).warn("Caught unexpected exception:", exception);
+  }
+}
diff --git a/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/callback/SessionExpirationCacheListenerJUnitTest.java b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/callback/SessionExpirationCacheListenerJUnitTest.java
new file mode 100644
index 0000000..7228fe8
--- /dev/null
+++ b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/callback/SessionExpirationCacheListenerJUnitTest.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.modules.session.catalina.callback;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import javax.servlet.http.HttpSession;
+
+import org.junit.Test;
+
+import org.apache.geode.cache.EntryEvent;
+import org.apache.geode.cache.Operation;
+import org.apache.geode.modules.session.catalina.DeltaSession;
+
+public class SessionExpirationCacheListenerJUnitTest {
+  @Test
+  public void TestAfterDestroyProcessesSessionExpiredByGemfire() {
+    SessionExpirationCacheListener listener = new SessionExpirationCacheListener();
+    EntryEvent<String, HttpSession> event = mock(EntryEvent.class);
+    DeltaSession session = mock(DeltaSession.class);
+
+    when(event.getOperation()).thenReturn(Operation.EXPIRE_DESTROY);
+    when(event.getOldValue()).thenReturn(session);
+
+    listener.afterDestroy(event);
+
+    verify(session).processExpired();
+  }
+}
diff --git a/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionAttributeEventBatchJUnitTest.java b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionAttributeEventBatchJUnitTest.java
new file mode 100644
index 0000000..bee73b8
--- /dev/null
+++ b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionAttributeEventBatchJUnitTest.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.modules.session.catalina.internal;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Test;
+
+import org.apache.geode.LogWriter;
+import org.apache.geode.cache.Cache;
+import org.apache.geode.cache.Region;
+import org.apache.geode.modules.session.catalina.DeltaSessionInterface;
+
+public class DeltaSessionAttributeEventBatchJUnitTest {
+  String regionName = "regionName";
+  String sessionId = "sessionId";
+  LogWriter logWriter = mock(LogWriter.class);
+
+  @Test
+  public void TestApplyForBatch() {
+
+    List<DeltaSessionAttributeEvent> eventList = new ArrayList<>();
+    DeltaSessionAttributeEvent event1 = mock(DeltaSessionAttributeEvent.class);
+    DeltaSessionAttributeEvent event2 = mock(DeltaSessionAttributeEvent.class);
+    eventList.add(event1);
+    eventList.add(event2);
+
+
+    Cache cache = mock(Cache.class);
+    Region<String, DeltaSessionInterface> region = mock(Region.class);
+    DeltaSessionInterface deltaSessionInterface = mock(DeltaSessionInterface.class);
+
+    doReturn(region).when(cache).getRegion(regionName);
+    when(cache.getLogger()).thenReturn(logWriter);
+    when(logWriter.fineEnabled()).thenReturn(false);
+    when(region.get(sessionId)).thenReturn(deltaSessionInterface);
+
+    DeltaSessionAttributeEventBatch batch =
+        new DeltaSessionAttributeEventBatch(regionName, sessionId, eventList);
+
+    batch.apply(cache);
+
+    verify(deltaSessionInterface).applyAttributeEvents(region, eventList);
+  }
+}
diff --git a/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionDestroyAttributeEventJUnitTest.java b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionDestroyAttributeEventJUnitTest.java
new file mode 100644
index 0000000..5ea2648
--- /dev/null
+++ b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionDestroyAttributeEventJUnitTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.modules.session.catalina.internal;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Test;
+
+import org.apache.geode.modules.session.catalina.DeltaSessionInterface;
+
+public class DeltaSessionDestroyAttributeEventJUnitTest {
+  @Test
+  public void DeltaSessionDestroyAttributeEventAppliesAttributeToSession() {
+    String attributeName = "DestroyAttribute";
+
+    DeltaSessionDestroyAttributeEvent event = new DeltaSessionDestroyAttributeEvent(attributeName);
+    DeltaSessionInterface deltaSessionInterface = mock(DeltaSessionInterface.class);
+    event.apply((deltaSessionInterface));
+
+    verify(deltaSessionInterface).localDestroyAttribute(attributeName);
+  }
+}
diff --git a/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionStatisticsJUnitTest.java b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionStatisticsJUnitTest.java
new file mode 100644
index 0000000..51b8129
--- /dev/null
+++ b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionStatisticsJUnitTest.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.modules.session.catalina.internal;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.junit.Test;
+
+import org.apache.geode.Statistics;
+import org.apache.geode.distributed.internal.InternalDistributedSystem;
+
+public class DeltaSessionStatisticsJUnitTest {
+
+  @Test
+  public void CreatedDeltaSessionStatisticsAccessProperStats() {
+    String appName = "DeltaSessionStatisticsTest";
+
+    InternalDistributedSystem internalDistributedSystem = mock(InternalDistributedSystem.class);
+    Statistics statistics = mock(Statistics.class);
+
+    when(internalDistributedSystem.createAtomicStatistics(any(), any())).thenReturn(statistics);
+
+    DeltaSessionStatistics deltaSessionStatistics =
+        new DeltaSessionStatistics(internalDistributedSystem, appName);
+
+    deltaSessionStatistics.incSessionsCreated();
+    deltaSessionStatistics.incSessionsExpired();
+    deltaSessionStatistics.incSessionsInvalidated();
+
+    deltaSessionStatistics.getSessionsCreated();
+    deltaSessionStatistics.getSessionsExpired();
+    deltaSessionStatistics.getSessionsInvalidated();
+
+    verify(statistics, times(3)).incLong(anyInt(), anyLong());
+    verify(statistics, times(3)).getLong(anyInt());
+  }
+}
diff --git a/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionUpdateAttributeEventJUnitTest.java b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionUpdateAttributeEventJUnitTest.java
new file mode 100644
index 0000000..d356afd
--- /dev/null
+++ b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/catalina/internal/DeltaSessionUpdateAttributeEventJUnitTest.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.modules.session.catalina.internal;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Test;
+
+import org.apache.geode.modules.session.catalina.DeltaSessionInterface;
+
+public class DeltaSessionUpdateAttributeEventJUnitTest {
+  @Test
+  public void DeltaSessionDestroyAttributeEventAppliesAttributeToSession() {
+    String attributeName = "UpdateAttribute";
+    String attributeValue = "UpdateValue";
+
+    DeltaSessionUpdateAttributeEvent event =
+        new DeltaSessionUpdateAttributeEvent(attributeName, attributeValue);
+    DeltaSessionInterface deltaSessionInterface = mock(DeltaSessionInterface.class);
+    event.apply((deltaSessionInterface));
+
+    verify(deltaSessionInterface).localUpdateAttribute(attributeName, attributeValue);
+  }
+}
diff --git a/extensions/geode-modules/src/test/java/org/apache/geode/modules/util/CreateRegionFunctionJUnitTest.java b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/util/CreateRegionFunctionJUnitTest.java
similarity index 100%
rename from extensions/geode-modules/src/test/java/org/apache/geode/modules/util/CreateRegionFunctionJUnitTest.java
rename to extensions/geode-modules-test/src/main/java/org/apache/geode/modules/util/CreateRegionFunctionJUnitTest.java
diff --git a/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/util/TouchPartitionedRegionEntriesFunctionJUnitTest.java b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/util/TouchPartitionedRegionEntriesFunctionJUnitTest.java
new file mode 100644
index 0000000..6852681
--- /dev/null
+++ b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/util/TouchPartitionedRegionEntriesFunctionJUnitTest.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.modules.util;
+
+
+import static org.assertj.core.internal.bytebuddy.matcher.ElementMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+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 java.util.HashSet;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.geode.LogWriter;
+import org.apache.geode.cache.Cache;
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.execute.FunctionContext;
+import org.apache.geode.cache.execute.RegionFunctionContext;
+import org.apache.geode.cache.execute.ResultSender;
+
+public class TouchPartitionedRegionEntriesFunctionJUnitTest {
+
+  private TouchPartitionedRegionEntriesFunction function =
+      spy(new TouchPartitionedRegionEntriesFunction());
+  private FunctionContext context = mock(RegionFunctionContext.class);
+  private Cache cache = mock(Cache.class);
+  private LogWriter logger = mock(LogWriter.class);
+  private Region primaryDataSet = mock(Region.class);
+  private ResultSender resultSender = mock(ResultSender.class);
+
+  @Before
+  public void setUp() {
+    when(context.getCache()).thenReturn(cache);
+    when(context.getResultSender()).thenReturn(resultSender);
+    when(cache.getLogger()).thenReturn(logger);
+    when(logger.fineEnabled()).thenReturn(false);
+    doReturn(primaryDataSet).when(function)
+        .getLocalDataForContextViaRegionHelper((RegionFunctionContext) context);
+  }
+
+  @Test
+  public void executeDoesNotThrowExceptionWithProperlyDefinedContext() {
+    doReturn(new HashSet() {}).when((RegionFunctionContext) context).getFilter();
+
+    function.execute(context);
+
+    verify(primaryDataSet, times(0)).get(any());
+    verify(resultSender).lastResult(true);
+  }
+
+  @Test
+  public void executeDoesNotThrowExceptionWithProperlyDefinedContextAndMultipleKeys() {
+    HashSet<String> keys = new HashSet();
+    keys.add("Key1");
+    keys.add("Key2");
+
+    doReturn(keys).when((RegionFunctionContext) context).getFilter();
+
+    function.execute(context);
+
+    verify(primaryDataSet, times(keys.size())).get(anyString());
+    verify(resultSender).lastResult(true);
+  }
+}
diff --git a/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/util/TouchReplicatedRegionEntriesFunctionJUnitTest.java b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/util/TouchReplicatedRegionEntriesFunctionJUnitTest.java
new file mode 100644
index 0000000..294317a
--- /dev/null
+++ b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/util/TouchReplicatedRegionEntriesFunctionJUnitTest.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.modules.util;
+
+import static org.mockito.Mockito.mock;
+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 java.util.HashSet;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.geode.LogWriter;
+import org.apache.geode.cache.Cache;
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.execute.FunctionContext;
+import org.apache.geode.cache.execute.RegionFunctionContext;
+import org.apache.geode.cache.execute.ResultSender;
+
+public class TouchReplicatedRegionEntriesFunctionJUnitTest {
+  private TouchReplicatedRegionEntriesFunction function =
+      spy(new TouchReplicatedRegionEntriesFunction());
+  private FunctionContext context = mock(RegionFunctionContext.class);
+  private Cache cache = mock(Cache.class);
+  private LogWriter logger = mock(LogWriter.class);
+  private Region region = mock(Region.class);
+  private ResultSender resultSender = mock(ResultSender.class);
+  private String regionName = "regionName";
+  private HashSet<String> keys = new HashSet<>();
+  private Object[] arguments = new Object[] {regionName, keys};
+
+  @Before
+  public void setUp() {
+    when(context.getArguments()).thenReturn(arguments);
+    when(context.getCache()).thenReturn(cache);
+    when(context.getResultSender()).thenReturn(resultSender);
+    when(cache.getLogger()).thenReturn(logger);
+    when(logger.fineEnabled()).thenReturn(false);
+  }
+
+  @Test
+  public void executeDoesNotThrowExceptionWithProperlyDefinedContext() {
+    when(cache.getRegion(regionName)).thenReturn(region);
+
+    function.execute(context);
+
+    verify(region).getAll(keys);
+    verify(resultSender).lastResult(true);
+  }
+
+  @Test
+  public void executeDoesNotThrowExceptionWithProperlyDefinedContextAndNullRegion() {
+    when(cache.getRegion(regionName)).thenReturn(null);
+
+    function.execute(context);
+
+    verify(region, times(0)).getAll(keys);
+    verify(resultSender).lastResult(true);
+  }
+}
diff --git a/extensions/geode-modules-tomcat7/build.gradle b/extensions/geode-modules-tomcat7/build.gradle
index f5ff7e9..4c390ba 100644
--- a/extensions/geode-modules-tomcat7/build.gradle
+++ b/extensions/geode-modules-tomcat7/build.gradle
@@ -34,6 +34,10 @@
     // Remove everything related to Tomcat 6.x
     exclude group: 'org.apache.tomcat'
   }
+  testCompile(project(':extensions:geode-modules')) {
+    // Remove everything related to Tomcat 6.x
+    exclude group: 'org.apache.tomcat'
+  }
 
   compile('org.apache.tomcat:tomcat-catalina:' + DependencyConstraints.get('tomcat7.version')) {
     exclude module: 'tomcat-annotations-api'
@@ -44,6 +48,12 @@
   }
   compile('org.apache.tomcat:tomcat-juli:' + DependencyConstraints.get('tomcat7.version'))
 
+
+  testCompile('org.httpunit:httpunit')
+  testCompile('junit:junit')
+  testCompile('org.assertj:assertj-core')
+  testCompile('org.mockito:mockito-core')
+  testCompile(project(':extensions:geode-modules-test'))
   integrationTestCompile(project(':geode-dunit')) {
     exclude module: 'geode-core'
   }
diff --git a/extensions/geode-modules-tomcat7/src/main/java/org/apache/geode/modules/session/catalina/Tomcat7DeltaSessionManager.java b/extensions/geode-modules-tomcat7/src/main/java/org/apache/geode/modules/session/catalina/Tomcat7DeltaSessionManager.java
index 7bbe80f..e965cc5 100644
--- a/extensions/geode-modules-tomcat7/src/main/java/org/apache/geode/modules/session/catalina/Tomcat7DeltaSessionManager.java
+++ b/extensions/geode-modules-tomcat7/src/main/java/org/apache/geode/modules/session/catalina/Tomcat7DeltaSessionManager.java
@@ -39,7 +39,7 @@
    */
   @Override
   public void startInternal() throws LifecycleException {
-    super.startInternal();
+    startInternalBase();
     if (getLogger().isDebugEnabled()) {
       getLogger().debug(this + ": Starting");
     }
@@ -69,7 +69,15 @@
     scheduleTimerTasks();
 
     this.started.set(true);
-    this.setState(LifecycleState.STARTING);
+    this.setLifecycleState(LifecycleState.STARTING);
+  }
+
+  void setLifecycleState(LifecycleState newState) throws LifecycleException {
+    this.setState(newState);
+  }
+
+  void startInternalBase() throws LifecycleException {
+    super.startInternal();
   }
 
   /**
@@ -80,7 +88,7 @@
    */
   @Override
   public void stopInternal() throws LifecycleException {
-    super.stopInternal();
+    stopInternalBase();
     if (getLogger().isDebugEnabled()) {
       getLogger().debug(this + ": Stopping");
     }
@@ -112,7 +120,15 @@
       unregisterCommitSessionValve();
     }
 
-    this.setState(LifecycleState.STOPPING);
+    setLifecycleState(LifecycleState.STOPPING);
+  }
+
+  void stopInternalBase() throws LifecycleException {
+    super.stopInternal();
+  }
+
+  void destroyInternalBase() throws LifecycleException {
+    super.destroyInternal();
   }
 
   /**
diff --git a/extensions/geode-modules-tomcat7/src/test/java/org/apache/geode/modules/session/catalina/Tomcat7DeltaSessionManagerJUnitTest.java b/extensions/geode-modules-tomcat7/src/test/java/org/apache/geode/modules/session/catalina/Tomcat7DeltaSessionManagerJUnitTest.java
new file mode 100644
index 0000000..f1bee51
--- /dev/null
+++ b/extensions/geode-modules-tomcat7/src/test/java/org/apache/geode/modules/session/catalina/Tomcat7DeltaSessionManagerJUnitTest.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.modules.session.catalina;
+
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import java.io.IOException;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.Pipeline;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.geode.internal.cache.GemFireCacheImpl;
+
+public class Tomcat7DeltaSessionManagerJUnitTest extends DeltaSessionManagerJUnitTest {
+  private Pipeline pipeline;
+
+  @Before
+  public void setup() {
+    manager = spy(new Tomcat7DeltaSessionManager());
+    initTest();
+    pipeline = mock(Pipeline.class);
+  }
+
+  @Test
+  public void startInternalSucceedsInitialRun()
+      throws LifecycleException, IOException, ClassNotFoundException {
+    doNothing().when((Tomcat7DeltaSessionManager) manager).startInternalBase();
+    doReturn(true).when(manager).isCommitValveEnabled();
+    doReturn(cache).when(manager).getAnyCacheInstance();
+    doReturn(true).when((GemFireCacheImpl) cache).isClient();
+    doNothing().when(manager).initSessionCache();
+    doReturn(pipeline).when(manager).getPipeline();
+
+    // Unit testing for load is handled in the parent DeltaSessionManagerJUnitTest class
+    doNothing().when(manager).load();
+
+    doNothing().when((Tomcat7DeltaSessionManager) manager)
+        .setLifecycleState(LifecycleState.STARTING);
+
+    assertThat(manager.started).isFalse();
+    ((Tomcat7DeltaSessionManager) manager).startInternal();
+    assertThat(manager.started).isTrue();
+    verify((Tomcat7DeltaSessionManager) manager).setLifecycleState(LifecycleState.STARTING);
+  }
+
+  @Test
+  public void startInternalDoesNotReinitializeManagerOnSubsequentCalls()
+      throws LifecycleException, IOException, ClassNotFoundException {
+    doNothing().when((Tomcat7DeltaSessionManager) manager).startInternalBase();
+    doReturn(true).when(manager).isCommitValveEnabled();
+    doReturn(cache).when(manager).getAnyCacheInstance();
+    doReturn(true).when((GemFireCacheImpl) cache).isClient();
+    doNothing().when(manager).initSessionCache();
+    doReturn(pipeline).when(manager).getPipeline();
+
+    // Unit testing for load is handled in the parent DeltaSessionManagerJUnitTest class
+    doNothing().when(manager).load();
+
+    doNothing().when((Tomcat7DeltaSessionManager) manager)
+        .setLifecycleState(LifecycleState.STARTING);
+
+    assertThat(manager.started).isFalse();
+    ((Tomcat7DeltaSessionManager) manager).startInternal();
+
+    // Verify that various initialization actions were performed
+    assertThat(manager.started).isTrue();
+    verify(manager).initializeSessionCache();
+    verify((Tomcat7DeltaSessionManager) manager).setLifecycleState(LifecycleState.STARTING);
+
+    // Rerun startInternal
+    ((Tomcat7DeltaSessionManager) manager).startInternal();
+
+    // Verify that the initialization actions were still only performed one time
+    verify(manager).initializeSessionCache();
+    verify((Tomcat7DeltaSessionManager) manager).setLifecycleState(LifecycleState.STARTING);
+  }
+
+  @Test
+  public void stopInternal() throws LifecycleException, IOException {
+    doNothing().when((Tomcat7DeltaSessionManager) manager).startInternalBase();
+    doNothing().when((Tomcat7DeltaSessionManager) manager).destroyInternalBase();
+    doReturn(true).when(manager).isCommitValveEnabled();
+
+    // Unit testing for unload is handled in the parent DeltaSessionManagerJUnitTest class
+    doNothing().when(manager).unload();
+
+    doNothing().when((Tomcat7DeltaSessionManager) manager)
+        .setLifecycleState(LifecycleState.STOPPING);
+
+    ((Tomcat7DeltaSessionManager) manager).stopInternal();
+
+    assertThat(manager.started).isFalse();
+    verify((Tomcat7DeltaSessionManager) manager).setLifecycleState(LifecycleState.STOPPING);
+  }
+
+  @Test
+  public void setContainerSetsProperContainerAndMaxInactiveInterval() {
+    Context container = mock(Context.class);
+    int containerMaxInactiveInterval = 3;
+
+    doReturn(containerMaxInactiveInterval).when(container).getSessionTimeout();
+
+    manager.setContainer(container);
+    verify(manager).setMaxInactiveInterval(containerMaxInactiveInterval * 60);
+  }
+}
diff --git a/extensions/geode-modules-tomcat8/build.gradle b/extensions/geode-modules-tomcat8/build.gradle
index f8c7730..721d08d 100644
--- a/extensions/geode-modules-tomcat8/build.gradle
+++ b/extensions/geode-modules-tomcat8/build.gradle
@@ -28,12 +28,23 @@
   distributedTestCompile('junit:junit')
   compile('mx4j:mx4j')
   distributedTestImplementation(project(':geode-logging'))
+  testCompile('org.httpunit:httpunit')
+  testCompile('org.apache.tomcat:tomcat-jaspic-api:' + DependencyConstraints.get('tomcat8.version'))
+  testCompile('org.httpunit:httpunit')
+  testCompile('junit:junit')
+  testCompile('org.assertj:assertj-core')
+  testCompile('org.mockito:mockito-core')
+  testCompile(project(':extensions:geode-modules-test'))
   distributedTestCompile('org.httpunit:httpunit')
   distributedTestCompile('org.apache.tomcat:tomcat-jaspic-api:' + DependencyConstraints.get('tomcat8.version'))
   compile(project(':geode-core'))
   compile(project(':extensions:geode-modules')) {
     exclude group: 'org.apache.tomcat'
   }
+  testCompile(project(':extensions:geode-modules')) {
+    exclude group: 'org.apache.tomcat'
+  }
+
 
   compile('org.apache.tomcat:tomcat-catalina:' + DependencyConstraints.get('tomcat8.version')) {
     exclude module: 'tomcat-annotations-api'
@@ -44,6 +55,16 @@
   }
   compile('org.apache.tomcat:tomcat-juli:' + DependencyConstraints.get('tomcat8.version'))
   compile('javax.servlet:javax.servlet-api')
+  
+  integrationTestCompile(project(':geode-dunit')) {
+    exclude module: 'geode-core'
+  }
+  integrationTestCompile(project(':geode-junit')) {
+    exclude module: 'geode-core'
+  }
+  integrationTestCompile(project(':extensions:geode-modules-test'))
+  integrationTestRuntime('xerces:xercesImpl')
+  integrationTestRuntime('javax.annotation:javax.annotation-api')
 
   distributedTestCompile(project(':geode-dunit')) {
     exclude module: 'geode-core'
diff --git a/extensions/geode-modules-tomcat8/src/main/java/org/apache/geode/modules/session/catalina/Tomcat8DeltaSessionManager.java b/extensions/geode-modules-tomcat8/src/main/java/org/apache/geode/modules/session/catalina/Tomcat8DeltaSessionManager.java
index 8752b61..8a32ac0 100644
--- a/extensions/geode-modules-tomcat8/src/main/java/org/apache/geode/modules/session/catalina/Tomcat8DeltaSessionManager.java
+++ b/extensions/geode-modules-tomcat8/src/main/java/org/apache/geode/modules/session/catalina/Tomcat8DeltaSessionManager.java
@@ -34,7 +34,7 @@
    */
   @Override
   public void startInternal() throws LifecycleException {
-    super.startInternal();
+    startInternalBase();
     if (getLogger().isDebugEnabled()) {
       getLogger().debug(this + ": Starting");
     }
@@ -64,7 +64,15 @@
     scheduleTimerTasks();
 
     this.started.set(true);
-    this.setState(LifecycleState.STARTING);
+    setLifecycleState(LifecycleState.STARTING);
+  }
+
+  void setLifecycleState(LifecycleState newState) throws LifecycleException {
+    this.setState(newState);
+  }
+
+  void startInternalBase() throws LifecycleException {
+    super.startInternal();
   }
 
   /**
@@ -75,7 +83,7 @@
    */
   @Override
   public void stopInternal() throws LifecycleException {
-    super.stopInternal();
+    stopInternalBase();
     if (getLogger().isDebugEnabled()) {
       getLogger().debug(this + ": Stopping");
     }
@@ -92,7 +100,7 @@
     // StandardManager expires all Sessions here.
     // All Sessions are not known by this Manager.
 
-    super.destroyInternal();
+    destroyInternalBase();
 
     // Clear any sessions to be touched
     getSessionsToTouch().clear();
@@ -107,10 +115,18 @@
       unregisterCommitSessionValve();
     }
 
-    this.setState(LifecycleState.STOPPING);
+    setLifecycleState(LifecycleState.STOPPING);
 
   }
 
+  void stopInternalBase() throws LifecycleException {
+    super.stopInternal();
+  }
+
+  void destroyInternalBase() throws LifecycleException {
+    super.destroyInternal();
+  }
+
   @Override
   public int getMaxInactiveInterval() {
     return getContext().getSessionTimeout();
diff --git a/extensions/geode-modules-tomcat8/src/test/java/org/apache/geode/modules/session/catalina/Tomcat8DeltaSessionManagerJUnitTest.java b/extensions/geode-modules-tomcat8/src/test/java/org/apache/geode/modules/session/catalina/Tomcat8DeltaSessionManagerJUnitTest.java
new file mode 100644
index 0000000..1bf7939
--- /dev/null
+++ b/extensions/geode-modules-tomcat8/src/test/java/org/apache/geode/modules/session/catalina/Tomcat8DeltaSessionManagerJUnitTest.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.modules.session.catalina;
+
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import java.io.IOException;
+
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.Pipeline;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.geode.internal.cache.GemFireCacheImpl;
+
+public class Tomcat8DeltaSessionManagerJUnitTest extends DeltaSessionManagerJUnitTest {
+  private Pipeline pipeline;
+
+  @Before
+  public void setup() {
+    manager = spy(new Tomcat8DeltaSessionManager());
+    initTest();
+    pipeline = mock(Pipeline.class);
+    doReturn(context).when(manager).getContext();
+  }
+
+  @Test
+  public void startInternalSucceedsInitialRun()
+      throws LifecycleException, IOException, ClassNotFoundException {
+    doNothing().when((Tomcat8DeltaSessionManager) manager).startInternalBase();
+    doReturn(true).when(manager).isCommitValveEnabled();
+    doReturn(cache).when(manager).getAnyCacheInstance();
+    doReturn(true).when((GemFireCacheImpl) cache).isClient();
+    doNothing().when(manager).initSessionCache();
+    doReturn(pipeline).when(manager).getPipeline();
+
+    // Unit testing for load is handled in the parent DeltaSessionManagerJUnitTest class
+    doNothing().when(manager).load();
+
+    doNothing().when((Tomcat8DeltaSessionManager) manager)
+        .setLifecycleState(LifecycleState.STARTING);
+
+    assertThat(manager.started).isFalse();
+    ((Tomcat8DeltaSessionManager) manager).startInternal();
+    assertThat(manager.started).isTrue();
+    verify((Tomcat8DeltaSessionManager) manager).setLifecycleState(LifecycleState.STARTING);
+  }
+
+  @Test
+  public void startInternalDoesNotReinitializeManagerOnSubsequentCalls()
+      throws LifecycleException, IOException, ClassNotFoundException {
+    doNothing().when((Tomcat8DeltaSessionManager) manager).startInternalBase();
+    doReturn(true).when(manager).isCommitValveEnabled();
+    doReturn(cache).when(manager).getAnyCacheInstance();
+    doReturn(true).when((GemFireCacheImpl) cache).isClient();
+    doNothing().when(manager).initSessionCache();
+    doReturn(pipeline).when(manager).getPipeline();
+
+    // Unit testing for load is handled in the parent DeltaSessionManagerJUnitTest class
+    doNothing().when(manager).load();
+
+    doNothing().when((Tomcat8DeltaSessionManager) manager)
+        .setLifecycleState(LifecycleState.STARTING);
+
+    assertThat(manager.started).isFalse();
+    ((Tomcat8DeltaSessionManager) manager).startInternal();
+
+    // Verify that various initialization actions were performed
+    assertThat(manager.started).isTrue();
+    verify(manager).initializeSessionCache();
+    verify((Tomcat8DeltaSessionManager) manager).setLifecycleState(LifecycleState.STARTING);
+
+    // Rerun startInternal
+    ((Tomcat8DeltaSessionManager) manager).startInternal();
+
+    // Verify that the initialization actions were still only performed one time
+    verify(manager).initializeSessionCache();
+    verify((Tomcat8DeltaSessionManager) manager).setLifecycleState(LifecycleState.STARTING);
+  }
+
+  @Test
+  public void stopInternal() throws LifecycleException, IOException {
+    doNothing().when((Tomcat8DeltaSessionManager) manager).startInternalBase();
+    doNothing().when((Tomcat8DeltaSessionManager) manager).destroyInternalBase();
+    doReturn(true).when(manager).isCommitValveEnabled();
+
+    // Unit testing for unload is handled in the parent DeltaSessionManagerJUnitTest class
+    doNothing().when(manager).unload();
+
+    doNothing().when((Tomcat8DeltaSessionManager) manager)
+        .setLifecycleState(LifecycleState.STOPPING);
+
+    ((Tomcat8DeltaSessionManager) manager).stopInternal();
+
+    assertThat(manager.started).isFalse();
+    verify((Tomcat8DeltaSessionManager) manager).setLifecycleState(LifecycleState.STOPPING);
+  }
+
+}
diff --git a/extensions/geode-modules-tomcat9/build.gradle b/extensions/geode-modules-tomcat9/build.gradle
index 280e530..417714a 100644
--- a/extensions/geode-modules-tomcat9/build.gradle
+++ b/extensions/geode-modules-tomcat9/build.gradle
@@ -28,6 +28,9 @@
   compile(project(':extensions:geode-modules')) {
     exclude group: 'org.apache.tomcat'
   }
+  testCompile(project(':extensions:geode-modules')) {
+    exclude group: 'org.apache.tomcat'
+  }
 
   compile('org.apache.tomcat:tomcat-catalina:' + DependencyConstraints.get('tomcat9.version')) {
     exclude module: 'tomcat-annotations-api'
@@ -39,6 +42,12 @@
   compile('org.apache.tomcat:tomcat-juli:' + DependencyConstraints.get('tomcat9.version'))
   compile('javax.servlet:javax.servlet-api:' + '3.1.0')
 
+  testCompile('org.httpunit:httpunit')
+  testCompile('junit:junit')
+  testCompile('org.assertj:assertj-core')
+  testCompile('org.mockito:mockito-core')
+  testCompile(project(':extensions:geode-modules-test')) 
+
   distributedTestCompile(project(':extensions:geode-modules-test'))
 
   distributedTestRuntime('xerces:xercesImpl')
diff --git a/extensions/geode-modules-tomcat9/src/main/java/org/apache/geode/modules/session/catalina/Tomcat9DeltaSessionManager.java b/extensions/geode-modules-tomcat9/src/main/java/org/apache/geode/modules/session/catalina/Tomcat9DeltaSessionManager.java
index 973bb3e..ac9ac2c 100644
--- a/extensions/geode-modules-tomcat9/src/main/java/org/apache/geode/modules/session/catalina/Tomcat9DeltaSessionManager.java
+++ b/extensions/geode-modules-tomcat9/src/main/java/org/apache/geode/modules/session/catalina/Tomcat9DeltaSessionManager.java
@@ -34,7 +34,7 @@
    */
   @Override
   public void startInternal() throws LifecycleException {
-    super.startInternal();
+    startInternalBase();
     if (getLogger().isDebugEnabled()) {
       getLogger().debug(this + ": Starting");
     }
@@ -64,7 +64,15 @@
     scheduleTimerTasks();
 
     this.started.set(true);
-    this.setState(LifecycleState.STARTING);
+    setLifecycleState(LifecycleState.STARTING);
+  }
+
+  void setLifecycleState(LifecycleState newState) throws LifecycleException {
+    this.setState(newState);
+  }
+
+  void startInternalBase() throws LifecycleException {
+    super.startInternal();
   }
 
   /**
@@ -75,7 +83,7 @@
    */
   @Override
   public void stopInternal() throws LifecycleException {
-    super.stopInternal();
+    stopInternalBase();
     if (getLogger().isDebugEnabled()) {
       getLogger().debug(this + ": Stopping");
     }
@@ -92,7 +100,7 @@
     // StandardManager expires all Sessions here.
     // All Sessions are not known by this Manager.
 
-    super.destroyInternal();
+    destroyInternalBase();
 
     // Clear any sessions to be touched
     getSessionsToTouch().clear();
@@ -107,10 +115,18 @@
       unregisterCommitSessionValve();
     }
 
-    this.setState(LifecycleState.STOPPING);
+    setLifecycleState(LifecycleState.STOPPING);
 
   }
 
+  void stopInternalBase() throws LifecycleException {
+    super.stopInternal();
+  }
+
+  void destroyInternalBase() throws LifecycleException {
+    super.destroyInternal();
+  }
+
   @Override
   public int getMaxInactiveInterval() {
     return getContext().getSessionTimeout();
diff --git a/extensions/geode-modules-tomcat9/src/test/java/org/apache/geode/modules/session/catalina/Tomcat9DeltaSessionManagerJUnitTest.java b/extensions/geode-modules-tomcat9/src/test/java/org/apache/geode/modules/session/catalina/Tomcat9DeltaSessionManagerJUnitTest.java
new file mode 100644
index 0000000..cc55763
--- /dev/null
+++ b/extensions/geode-modules-tomcat9/src/test/java/org/apache/geode/modules/session/catalina/Tomcat9DeltaSessionManagerJUnitTest.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.modules.session.catalina;
+
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import java.io.IOException;
+
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.Pipeline;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.geode.internal.cache.GemFireCacheImpl;
+
+public class Tomcat9DeltaSessionManagerJUnitTest extends DeltaSessionManagerJUnitTest {
+  private Pipeline pipeline;
+
+  @Before
+  public void setup() {
+    manager = spy(new Tomcat9DeltaSessionManager());
+    initTest();
+    pipeline = mock(Pipeline.class);
+    doReturn(context).when(manager).getContext();
+  }
+
+  @Test
+  public void startInternalSucceedsInitialRun()
+      throws LifecycleException, IOException, ClassNotFoundException {
+    doNothing().when((Tomcat9DeltaSessionManager) manager).startInternalBase();
+    doReturn(true).when(manager).isCommitValveEnabled();
+    doReturn(cache).when(manager).getAnyCacheInstance();
+    doReturn(true).when((GemFireCacheImpl) cache).isClient();
+    doNothing().when(manager).initSessionCache();
+    doReturn(pipeline).when(manager).getPipeline();
+
+    // Unit testing for load is handled in the parent DeltaSessionManagerJUnitTest class
+    doNothing().when(manager).load();
+
+    doNothing().when((Tomcat9DeltaSessionManager) manager)
+        .setLifecycleState(LifecycleState.STARTING);
+
+    assertThat(manager.started).isFalse();
+    ((Tomcat9DeltaSessionManager) manager).startInternal();
+    assertThat(manager.started).isTrue();
+    verify((Tomcat9DeltaSessionManager) manager).setLifecycleState(LifecycleState.STARTING);
+  }
+
+  @Test
+  public void startInternalDoesNotReinitializeManagerOnSubsequentCalls()
+      throws LifecycleException, IOException, ClassNotFoundException {
+    doNothing().when((Tomcat9DeltaSessionManager) manager).startInternalBase();
+    doReturn(true).when(manager).isCommitValveEnabled();
+    doReturn(cache).when(manager).getAnyCacheInstance();
+    doReturn(true).when((GemFireCacheImpl) cache).isClient();
+    doNothing().when(manager).initSessionCache();
+    doReturn(pipeline).when(manager).getPipeline();
+
+    // Unit testing for load is handled in the parent DeltaSessionManagerJUnitTest class
+    doNothing().when(manager).load();
+
+    doNothing().when((Tomcat9DeltaSessionManager) manager)
+        .setLifecycleState(LifecycleState.STARTING);
+
+    assertThat(manager.started).isFalse();
+    ((Tomcat9DeltaSessionManager) manager).startInternal();
+
+    // Verify that various initialization actions were performed
+    assertThat(manager.started).isTrue();
+    verify(manager).initializeSessionCache();
+    verify((Tomcat9DeltaSessionManager) manager).setLifecycleState(LifecycleState.STARTING);
+
+    // Rerun startInternal
+    ((Tomcat9DeltaSessionManager) manager).startInternal();
+
+    // Verify that the initialization actions were still only performed one time
+    verify(manager).initializeSessionCache();
+    verify((Tomcat9DeltaSessionManager) manager).setLifecycleState(LifecycleState.STARTING);
+  }
+
+  @Test
+  public void stopInternal() throws LifecycleException, IOException {
+    doNothing().when((Tomcat9DeltaSessionManager) manager).startInternalBase();
+    doNothing().when((Tomcat9DeltaSessionManager) manager).destroyInternalBase();
+    doReturn(true).when(manager).isCommitValveEnabled();
+
+    // Unit testing for unload is handled in the parent DeltaSessionManagerJUnitTest class
+    doNothing().when(manager).unload();
+
+    doNothing().when((Tomcat9DeltaSessionManager) manager)
+        .setLifecycleState(LifecycleState.STOPPING);
+
+    ((Tomcat9DeltaSessionManager) manager).stopInternal();
+
+    assertThat(manager.started).isFalse();
+    verify((Tomcat9DeltaSessionManager) manager).setLifecycleState(LifecycleState.STOPPING);
+  }
+
+}
diff --git a/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/AbstractSessionCache.java b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/AbstractSessionCache.java
index 254bf57..f4aada3 100644
--- a/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/AbstractSessionCache.java
+++ b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/AbstractSessionCache.java
@@ -96,7 +96,7 @@
   }
 
   RegionConfiguration createRegionConfiguration() {
-    RegionConfiguration configuration = new RegionConfiguration();
+    RegionConfiguration configuration = getNewRegionConfiguration();
     configuration.setRegionName(getSessionManager().getRegionName());
     configuration.setRegionAttributesId(getSessionManager().getRegionAttributesId());
     if (getSessionManager()
@@ -110,4 +110,9 @@
     configuration.setEnableDebugListener(getSessionManager().getEnableDebugListener());
     return configuration;
   }
+
+  // Helper methods added to improve unit testing of class
+  RegionConfiguration getNewRegionConfiguration() {
+    return new RegionConfiguration();
+  }
 }
diff --git a/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/ClientServerSessionCache.java b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/ClientServerSessionCache.java
index 0803a4c..952e4cd 100644
--- a/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/ClientServerSessionCache.java
+++ b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/ClientServerSessionCache.java
@@ -98,7 +98,7 @@
     // Invoke the appropriate function depending on the type of region
     if (regionAttributesID.startsWith("partition")) {
       // Execute the partitioned touch function on the primary server(s)
-      Execution execution = FunctionService.onRegion(getSessionRegion()).withFilter(sessionIds);
+      Execution execution = getExecutionForFunctionOnRegionWithFilter(sessionIds);
       try {
         ResultCollector collector = execution.execute(TouchPartitionedRegionEntriesFunction.ID);
         collector.getResult();
@@ -108,8 +108,8 @@
       }
     } else {
       // Execute the member touch function on all the server(s)
-      Execution execution = FunctionService.onServers(getCache())
-          .setArguments(new Object[] {this.sessionRegion.getFullPath(), sessionIds});
+      Object[] arguments = new Object[] {this.sessionRegion.getFullPath(), sessionIds};
+      Execution execution = getExecutionForFunctionOnServersWithArguments(arguments);
       try {
         ResultCollector collector = execution.execute(TouchReplicatedRegionEntriesFunction.ID);
         collector.getResult();
@@ -143,7 +143,7 @@
   @Override
   public boolean isBackingCacheAvailable() {
     if (getSessionManager().isCommitValveFailfastEnabled()) {
-      PoolImpl pool = (PoolImpl) PoolManager.find(getOperatingRegionName());
+      PoolImpl pool = findPoolInPoolManager();
       return pool.isPrimaryUpdaterAlive();
     }
     return true;
@@ -155,7 +155,7 @@
   }
 
   private void bootstrapServers() {
-    Execution execution = FunctionService.onServers(this.cache);
+    Execution execution = getExecutionForFunctionOnServers();
     ResultCollector collector = execution.execute(new BootstrappingFunction());
     // Get the result. Nothing is being done with it.
     try {
@@ -202,12 +202,12 @@
         .anyMatch(x -> x instanceof SessionExpirationCacheListener);
   }
 
-  private void createSessionRegionOnServers() {
+  void createSessionRegionOnServers() {
     // Create the RegionConfiguration
     RegionConfiguration configuration = createRegionConfiguration();
 
     // Send it to the server tier
-    Execution execution = FunctionService.onServer(this.cache).setArguments(configuration);
+    Execution execution = getExecutionForFunctionOnServerWithRegionConfiguration(configuration);
     ResultCollector collector = execution.execute(CreateRegionFunction.ID);
 
     // Verify the region was successfully created on the servers
@@ -225,7 +225,7 @@
     }
   }
 
-  private Region<String, HttpSession> createLocalSessionRegion() {
+  Region<String, HttpSession> createLocalSessionRegion() {
     ClientRegionFactory<String, HttpSession> factory = null;
     if (getSessionManager().getEnableLocalCache()) {
       // Create the region factory with caching and heap LRU enabled
@@ -257,4 +257,37 @@
 
     return region;
   }
+
+  // Helper methods added to improve unit testing of class
+  Execution getExecutionForFunctionOnServers() {
+    return getExecutionForFunctionOnServersWithArguments(null);
+  }
+
+  Execution getExecutionForFunctionOnServersWithArguments(Object[] arguments) {
+    if (arguments != null && arguments.length > 0) {
+      return FunctionService.onServers(getCache()).setArguments(arguments);
+    } else {
+      return FunctionService.onServers(getCache());
+    }
+  }
+
+  Execution getExecutionForFunctionOnServerWithRegionConfiguration(RegionConfiguration arguments) {
+    if (arguments != null) {
+      return FunctionService.onServer(getCache()).setArguments(arguments);
+    } else {
+      return FunctionService.onServer(getCache());
+    }
+  }
+
+  Execution getExecutionForFunctionOnRegionWithFilter(Set<?> filter) {
+    if (filter != null && filter.size() > 0) {
+      return FunctionService.onRegion(getSessionRegion()).withFilter(filter);
+    } else {
+      return FunctionService.onRegion(getSessionRegion());
+    }
+  }
+
+  PoolImpl findPoolInPoolManager() {
+    return (PoolImpl) PoolManager.find(getOperatingRegionName());
+  }
 }
diff --git a/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/DeltaSession.java b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/DeltaSession.java
index 0be6d03..aa8b3f5 100644
--- a/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/DeltaSession.java
+++ b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/DeltaSession.java
@@ -115,10 +115,9 @@
   @SuppressWarnings("unchecked")
   public HttpSession getSession() {
     if (facade == null) {
-      if (SecurityUtil.isPackageProtectionEnabled()) {
+      if (isPackageProtectionEnabled()) {
         final DeltaSession fsession = this;
-        facade = (DeltaSessionFacade) AccessController.doPrivileged(
-            (PrivilegedAction) () -> new DeltaSessionFacade(fsession));
+        facade = getNewFacade(fsession);
       } else {
         facade = new DeltaSessionFacade(this);
       }
@@ -191,7 +190,7 @@
     return this.operatingRegion;
   }
 
-  private boolean isCommitEnabled() {
+  boolean isCommitEnabled() {
     DeltaSessionManager mgr = (DeltaSessionManager) getManager();
     return mgr.isCommitValveEnabled();
   }
@@ -240,7 +239,9 @@
 
   @Override
   public void setAttribute(String name, Object value, boolean notify) {
+
     checkBackingCacheAvailable();
+
     synchronized (this.changeLock) {
       // Serialize the value
       byte[] serializedValue = serialize(value);
@@ -261,6 +262,7 @@
           new DeltaSessionUpdateAttributeEvent(name, serializedValue);
       queueAttributeEvent(event, true);
 
+
       // Distribute the update
       if (!isCommitEnabled()) {
         putInRegion(getOperatingRegion(), true, null);
@@ -318,6 +320,10 @@
     return value;
   }
 
+  Object getAttributeWithoutDeserialize(String name) {
+    return super.getAttribute(name);
+  }
+
   @Override
   public void invalidate() {
     super.invalidate();
@@ -414,7 +420,7 @@
         }
       }
     }
-    this.eventQueue.add(event);
+    addEventToEventQueue(event);
   }
 
   @SuppressWarnings("unchecked")
@@ -600,8 +606,8 @@
     @SuppressWarnings("unchecked")
     Enumeration<String> attributeNames = (Enumeration<String>) getAttributeNames();
     while (attributeNames.hasMoreElements()) {
-      // Don't use this.getAttribute() because we don't want to deserialize the value.
-      Object value = super.getAttribute(attributeNames.nextElement());
+      // Don't use getAttribute() because we don't want to deserialize the value.
+      Object value = getAttributeWithoutDeserialize(attributeNames.nextElement());
       if (value instanceof byte[]) {
         size += ((byte[]) value).length;
       }
@@ -634,10 +640,10 @@
     throw new IllegalStateException("Unable to access attributes field");
   }
 
-  private byte[] serialize(Object obj) {
+  byte[] serialize(Object obj) {
     byte[] serializedValue = null;
     try {
-      serializedValue = BlobHelper.serializeToBlob(obj);
+      serializedValue = serializeViaBlobHelper(obj);
     } catch (IOException e) {
       String builder = this + ": Object " + obj
           + " cannot be serialized due to the following exception";
@@ -647,6 +653,11 @@
     return serializedValue;
   }
 
+  byte[] serializeViaBlobHelper(Object obj) throws IOException {
+    return BlobHelper.serializeToBlob(obj);
+  }
+
+
   @Override
   public String toString() {
     return "DeltaSession[" + "id=" + getId()
@@ -655,4 +666,18 @@
         + (getOperatingRegion() == null ? "unset" : getOperatingRegion().getFullPath())
         + "]";
   }
+
+  // Helper methods to enable better unit testing
+  DeltaSessionFacade getNewFacade(DeltaSessionInterface fSession) {
+    return (DeltaSessionFacade) AccessController.doPrivileged(
+        (PrivilegedAction) () -> new DeltaSessionFacade(fSession));
+  }
+
+  boolean isPackageProtectionEnabled() {
+    return SecurityUtil.isPackageProtectionEnabled();
+  }
+
+  void addEventToEventQueue(DeltaSessionAttributeEvent event) {
+    eventQueue.add(event);
+  }
 }
diff --git a/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/DeltaSessionManager.java b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/DeltaSessionManager.java
index be1142d..09dacde 100644
--- a/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/DeltaSessionManager.java
+++ b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/DeltaSessionManager.java
@@ -29,6 +29,7 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Set;
 import java.util.Timer;
 import java.util.TimerTask;
@@ -49,6 +50,7 @@
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
 
+import org.apache.geode.cache.Cache;
 import org.apache.geode.cache.CacheFactory;
 import org.apache.geode.cache.Region;
 import org.apache.geode.cache.query.Query;
@@ -63,6 +65,9 @@
 public abstract class DeltaSessionManager extends ManagerBase
     implements Lifecycle, PropertyChangeListener, SessionManager {
 
+  static final String catalinaBaseSystemProperty = "catalina.base";
+  static final String javaTempDirSystemProperty = "java.io.tmpdir";
+  static final String fileSeparatorSystemProperty = "file.separator";
   /**
    * The number of rejected sessions.
    */
@@ -372,7 +377,7 @@
 
   protected void initializeSessionCache() {
     // Retrieve the cache
-    GemFireCacheImpl cache = (GemFireCacheImpl) CacheFactory.getAnyInstance();
+    GemFireCacheImpl cache = (GemFireCacheImpl) getAnyCacheInstance();
     if (cache == null) {
       throw new IllegalStateException(
           "No cache exists. Please configure either a PeerToPeerCacheLifecycleListener or ClientServerCacheLifecycleListener in the server.xml file.");
@@ -383,9 +388,17 @@
         : new PeerToPeerSessionCache(this, cache);
 
     // Initialize the session cache
+    initSessionCache();
+  }
+
+  void initSessionCache() {
     this.sessionCache.initialize();
   }
 
+  Cache getAnyCacheInstance() {
+    return CacheFactory.getAnyInstance();
+  }
+
   @Override
   protected StandardSession getNewSession() {
     return new DeltaSession(this);
@@ -570,7 +583,7 @@
     getPipeline().addValve(jvmRouteBinderValve);
   }
 
-  protected Pipeline getPipeline() {
+  Pipeline getPipeline() {
     return getContainer().getPipeline();
   }
 
@@ -660,7 +673,7 @@
    * @throws IOException if an input/output error occurs
    */
   private void doUnload() throws IOException {
-    QueryService querySvc = sessionCache.getCache().getQueryService();
+    QueryService querySvc = getSessionCache().getCache().getQueryService();
     Context context = getTheContext();
 
     if (context == null) {
@@ -707,9 +720,9 @@
     ObjectOutputStream oos = null;
     boolean error = false;
     try {
-      fos = new FileOutputStream(store.getAbsolutePath());
-      bos = new BufferedOutputStream(fos);
-      oos = new ObjectOutputStream(bos);
+      fos = getFileOutputStream(store);
+      bos = getBufferedOutputStream(fos);
+      oos = getObjectOutputStream(bos);
     } catch (IOException e) {
       error = true;
       getLogger().error("Exception unloading sessions", e);
@@ -755,7 +768,7 @@
     if (getLogger().isDebugEnabled())
       getLogger().debug("Unloading " + list.size() + " sessions");
     try {
-      oos.writeObject(list.size());
+      writeToObjectOutputStream(oos, list);
       for (DeltaSessionInterface session : list) {
         if (session instanceof StandardSession) {
           StandardSession standardSession = (StandardSession) session;
@@ -830,8 +843,8 @@
     Loader loader = null;
     ClassLoader classLoader = null;
     try {
-      fis = new FileInputStream(store.getAbsolutePath());
-      bis = new BufferedInputStream(fis);
+      fis = getFileInputStream(store);
+      bis = getBufferedInputStream(fis);
       if (getTheContext() != null) {
         loader = getTheContext().getLoader();
       }
@@ -847,7 +860,7 @@
         if (getLogger().isDebugEnabled()) {
           getLogger().debug("Creating standard object input stream");
         }
-        ois = new ObjectInputStream(bis);
+        ois = getObjectInputStream(bis);
       }
     } catch (FileNotFoundException e) {
       if (getLogger().isDebugEnabled()) {
@@ -871,7 +884,7 @@
 
     // Load the previously unloaded active sessions
     try {
-      int n = (Integer) ois.readObject();
+      int n = getSessionCountFromObjectInputStream(ois);
       if (getLogger().isDebugEnabled()) {
         getLogger().debug("Loading " + n + " persisted sessions");
       }
@@ -932,16 +945,57 @@
    * Return a File object representing the pathname to our persistence file, if any.
    */
   private File sessionStore(String ctxPath) {
-    String storeDir = System.getProperty("catalina.base");
+    String storeDir = getSystemPropertyValue(catalinaBaseSystemProperty);
     if (storeDir == null || storeDir.isEmpty()) {
-      storeDir = System.getProperty("java.io.tmpdir");
+      storeDir = getSystemPropertyValue(javaTempDirSystemProperty);
     } else {
-      storeDir += System.getProperty("file.separator") + "temp";
+      storeDir += getSystemPropertyValue(fileSeparatorSystemProperty) + "temp";
     }
 
+    return getFileAtPath(storeDir, ctxPath);
+  }
+
+  String getSystemPropertyValue(String propertyKey) {
+    return System.getProperty(propertyKey);
+  }
+
+  File getFileAtPath(String storeDir, String ctxPath) {
     return (new File(storeDir, ctxPath.replaceAll("/", "_") + ".sessions.ser"));
   }
 
+  FileInputStream getFileInputStream(File file) throws FileNotFoundException {
+    return new FileInputStream(file.getAbsolutePath());
+  }
+
+  BufferedInputStream getBufferedInputStream(FileInputStream fis) {
+    return new BufferedInputStream(fis);
+  }
+
+  ObjectInputStream getObjectInputStream(BufferedInputStream bis) throws IOException {
+    return new ObjectInputStream(bis);
+  }
+
+  FileOutputStream getFileOutputStream(File file) throws FileNotFoundException {
+    return new FileOutputStream(file.getAbsolutePath());
+  }
+
+  BufferedOutputStream getBufferedOutputStream(FileOutputStream fos) {
+    return new BufferedOutputStream(fos);
+  }
+
+  ObjectOutputStream getObjectOutputStream(BufferedOutputStream bos) throws IOException {
+    return new ObjectOutputStream(bos);
+  }
+
+  void writeToObjectOutputStream(ObjectOutputStream oos, List listToWrite) throws IOException {
+    oos.writeObject(listToWrite.size());
+  }
+
+  int getSessionCountFromObjectInputStream(ObjectInputStream ois)
+      throws IOException, ClassNotFoundException {
+    return (Integer) ois.readObject();
+  }
+
   @Override
   public String toString() {
     return getClass().getSimpleName() + "[" + "container="
diff --git a/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/PeerToPeerSessionCache.java b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/PeerToPeerSessionCache.java
index 7d4b941..d5031c1 100644
--- a/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/PeerToPeerSessionCache.java
+++ b/extensions/geode-modules/src/main/java/org/apache/geode/modules/session/catalina/PeerToPeerSessionCache.java
@@ -24,6 +24,7 @@
 import org.apache.geode.cache.RegionFactory;
 import org.apache.geode.cache.RegionShortcut;
 import org.apache.geode.cache.execute.Execution;
+import org.apache.geode.cache.execute.Function;
 import org.apache.geode.cache.execute.FunctionService;
 import org.apache.geode.cache.execute.ResultCollector;
 import org.apache.geode.modules.session.catalina.callback.LocalSessionCacheLoader;
@@ -87,12 +88,12 @@
     ResultCollector collector = null;
     if (regionAttributesID.startsWith("partition")) {
       // Execute the partitioned touch function on the primary server(s)
-      Execution execution = FunctionService.onRegion(getSessionRegion()).withFilter(sessionIds);
+      Execution execution = getExecutionForFunctionOnRegionWithFilter(sessionIds);
       collector = execution.execute(TouchPartitionedRegionEntriesFunction.ID);
     } else {
       // Execute the member touch function on all the server(s)
-      Execution execution = FunctionService.onMembers()
-          .setArguments(new Object[] {this.sessionRegion.getFullPath(), sessionIds});
+      Execution execution = getExecutionForFunctionOnMembersWithArguments(
+          new Object[] {this.sessionRegion.getFullPath(), sessionIds});
       collector = execution.execute(TouchReplicatedRegionEntriesFunction.ID);
     }
 
@@ -105,6 +106,7 @@
     }
   }
 
+
   @Override
   public boolean isPeerToPeer() {
     return true;
@@ -142,13 +144,13 @@
 
   private void registerFunctions() {
     // Register the touch partitioned region entries function if it is not already registered
-    if (!FunctionService.isRegistered(TouchPartitionedRegionEntriesFunction.ID)) {
-      FunctionService.registerFunction(new TouchPartitionedRegionEntriesFunction());
+    if (!isFunctionRegistered(TouchPartitionedRegionEntriesFunction.ID)) {
+      registerFunctionWithFunctionService(new TouchPartitionedRegionEntriesFunction());
     }
 
     // Register the touch replicated region entries function if it is not already registered
-    if (!FunctionService.isRegistered(TouchReplicatedRegionEntriesFunction.ID)) {
-      FunctionService.registerFunction(new TouchReplicatedRegionEntriesFunction());
+    if (!isFunctionRegistered(TouchReplicatedRegionEntriesFunction.ID)) {
+      registerFunctionWithFunctionService(new TouchReplicatedRegionEntriesFunction());
     }
   }
 
@@ -164,7 +166,7 @@
     Region region = this.cache.getRegion(getSessionManager().getRegionName());
     if (region == null) {
       // Create the region
-      region = RegionHelper.createRegion((Cache) getCache(), configuration);
+      region = createRegionUsingHelper(configuration);
       if (getSessionManager().getLogger().isDebugEnabled()) {
         getSessionManager().getLogger().debug("Created new session region: " + region);
       }
@@ -173,13 +175,21 @@
       if (getSessionManager().getLogger().isDebugEnabled()) {
         getSessionManager().getLogger().debug("Retrieved existing session region: " + region);
       }
-      RegionHelper.validateRegion((Cache) getCache(), configuration, region);
+      validateRegionUsingRegionhelper(configuration, region);
     }
 
     // Set the session region
     this.sessionRegion = region;
   }
 
+  void validateRegionUsingRegionhelper(RegionConfiguration configuration, Region region) {
+    RegionHelper.validateRegion((Cache) getCache(), configuration, region);
+  }
+
+  Region createRegionUsingHelper(RegionConfiguration configuration) {
+    return RegionHelper.createRegion((Cache) getCache(), configuration);
+  }
+
   private Region<String, HttpSession> createOrRetrieveLocalRegion() {
     // Attempt to retrieve the fronting region
     String frontingRegionName = this.sessionRegion.getName() + "_local";
@@ -215,4 +225,21 @@
     }
     return frontingRegion;
   }
+
+  // Helper methods added to improve unit testing of class
+  void registerFunctionWithFunctionService(Function function) {
+    FunctionService.registerFunction(function);
+  }
+
+  boolean isFunctionRegistered(String id) {
+    return FunctionService.isRegistered(id);
+  }
+
+  Execution getExecutionForFunctionOnRegionWithFilter(Set<String> sessionIds) {
+    return FunctionService.onRegion(getSessionRegion()).withFilter(sessionIds);
+  }
+
+  Execution getExecutionForFunctionOnMembersWithArguments(Object[] arguments) {
+    return FunctionService.onMembers().setArguments(arguments);
+  }
 }
diff --git a/extensions/geode-modules/src/main/java/org/apache/geode/modules/util/TouchPartitionedRegionEntriesFunction.java b/extensions/geode-modules/src/main/java/org/apache/geode/modules/util/TouchPartitionedRegionEntriesFunction.java
index 4f4049e..2fbeec5 100644
--- a/extensions/geode-modules/src/main/java/org/apache/geode/modules/util/TouchPartitionedRegionEntriesFunction.java
+++ b/extensions/geode-modules/src/main/java/org/apache/geode/modules/util/TouchPartitionedRegionEntriesFunction.java
@@ -47,7 +47,7 @@
 
     Cache cache = context.getCache();
     // Get local (primary) data for the context
-    Region primaryDataSet = PartitionRegionHelper.getLocalDataForContext(rfc);
+    Region primaryDataSet = getLocalDataForContextViaRegionHelper(rfc);
 
     if (cache.getLogger().fineEnabled()) {
       String builder = "Function " + ID + " received request to touch "
@@ -96,4 +96,9 @@
 
   @Override
   public void fromData(DataInput in) {}
+
+  // Helper methods added to improve unit testing of class
+  Region getLocalDataForContextViaRegionHelper(RegionFunctionContext rfc) {
+    return PartitionRegionHelper.getLocalDataForContext(rfc);
+  }
 }