aboutsummaryrefslogtreecommitdiff
path: root/core/src/test/java/com/android/volley/CacheDispatcherTest.java
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/test/java/com/android/volley/CacheDispatcherTest.java')
-rw-r--r--core/src/test/java/com/android/volley/CacheDispatcherTest.java276
1 files changed, 276 insertions, 0 deletions
diff --git a/core/src/test/java/com/android/volley/CacheDispatcherTest.java b/core/src/test/java/com/android/volley/CacheDispatcherTest.java
new file mode 100644
index 0000000..aef6785
--- /dev/null
+++ b/core/src/test/java/com/android/volley/CacheDispatcherTest.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.volley;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.android.volley.toolbox.StringRequest;
+import com.android.volley.utils.CacheTestUtils;
+import java.util.concurrent.BlockingQueue;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+@SuppressWarnings("rawtypes")
+public class CacheDispatcherTest {
+ private CacheDispatcher mDispatcher;
+ private @Mock BlockingQueue<Request<?>> mCacheQueue;
+ private @Mock BlockingQueue<Request<?>> mNetworkQueue;
+ private @Mock Cache mCache;
+ private @Mock ResponseDelivery mDelivery;
+ private @Mock Network mNetwork;
+ private StringRequest mRequest;
+
+ @Before
+ public void setUp() throws Exception {
+ initMocks(this);
+
+ mRequest = new StringRequest(Request.Method.GET, "http://foo", null, null);
+ mDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
+ }
+
+ private static class WaitForever implements Answer {
+ @Override
+ public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
+ Thread.sleep(Long.MAX_VALUE);
+ return null;
+ }
+ }
+
+ @Test
+ public void runStopsOnQuit() throws Exception {
+ when(mCacheQueue.take()).then(new WaitForever());
+ mDispatcher.start();
+ mDispatcher.quit();
+ mDispatcher.join(1000);
+ }
+
+ private static void verifyNoResponse(ResponseDelivery delivery) {
+ verify(delivery, never()).postResponse(any(Request.class), any(Response.class));
+ verify(delivery, never())
+ .postResponse(any(Request.class), any(Response.class), any(Runnable.class));
+ verify(delivery, never()).postError(any(Request.class), any(VolleyError.class));
+ }
+
+ // A cancelled request should not be processed at all.
+ @Test
+ public void cancelledRequest() throws Exception {
+ mRequest.cancel();
+ mDispatcher.processRequest(mRequest);
+ verify(mCache, never()).get(anyString());
+ verifyNoResponse(mDelivery);
+ }
+
+ // A cache miss does not post a response and puts the request on the network queue.
+ @Test
+ public void cacheMiss() throws Exception {
+ mDispatcher.processRequest(mRequest);
+ verifyNoResponse(mDelivery);
+ verify(mNetworkQueue).put(mRequest);
+ assertNull(mRequest.getCacheEntry());
+ }
+
+ // A non-expired cache hit posts a response and does not queue to the network.
+ @Test
+ public void nonExpiredCacheHit() throws Exception {
+ Cache.Entry entry = CacheTestUtils.makeRandomCacheEntry(null, false, false);
+ when(mCache.get(anyString())).thenReturn(entry);
+ mDispatcher.processRequest(mRequest);
+ verify(mDelivery).postResponse(any(Request.class), any(Response.class));
+ verify(mDelivery, never()).postError(any(Request.class), any(VolleyError.class));
+ }
+
+ // A soft-expired cache hit posts a response and queues to the network.
+ @Test
+ public void softExpiredCacheHit() throws Exception {
+ Cache.Entry entry = CacheTestUtils.makeRandomCacheEntry(null, false, true);
+ when(mCache.get(anyString())).thenReturn(entry);
+ mDispatcher.processRequest(mRequest);
+
+ // Soft expiration needs to use the deferred Runnable variant of postResponse,
+ // so make sure it gets to run.
+ ArgumentCaptor<Runnable> runnable = ArgumentCaptor.forClass(Runnable.class);
+ verify(mDelivery).postResponse(any(Request.class), any(Response.class), runnable.capture());
+ runnable.getValue().run();
+ // This way we can verify the behavior of the Runnable as well.
+ verify(mNetworkQueue).put(mRequest);
+ assertSame(entry, mRequest.getCacheEntry());
+
+ verify(mDelivery, never()).postError(any(Request.class), any(VolleyError.class));
+ }
+
+ // An expired cache hit does not post a response and queues to the network.
+ @Test
+ public void expiredCacheHit() throws Exception {
+ Cache.Entry entry = CacheTestUtils.makeRandomCacheEntry(null, true, true);
+ when(mCache.get(anyString())).thenReturn(entry);
+ mDispatcher.processRequest(mRequest);
+ verifyNoResponse(mDelivery);
+ verify(mNetworkQueue).put(mRequest);
+ assertSame(entry, mRequest.getCacheEntry());
+ }
+
+ // An fresh cache hit with parse error, does not post a response and queues to the network.
+ @Test
+ public void freshCacheHit_parseError() throws Exception {
+ Request request = mock(Request.class);
+ when(request.parseNetworkResponse(any(NetworkResponse.class)))
+ .thenReturn(Response.error(new ParseError()));
+ when(request.getCacheKey()).thenReturn("cache/key");
+ Cache.Entry entry = CacheTestUtils.makeRandomCacheEntry(null, false, false);
+ when(mCache.get(anyString())).thenReturn(entry);
+
+ mDispatcher.processRequest(request);
+
+ verifyNoResponse(mDelivery);
+ verify(mNetworkQueue).put(request);
+ assertNull(request.getCacheEntry());
+ verify(mCache).invalidate("cache/key", true);
+ verify(request).addMarker("cache-parsing-failed");
+ }
+
+ @Test
+ public void duplicateCacheMiss() throws Exception {
+ StringRequest secondRequest =
+ new StringRequest(Request.Method.GET, "http://foo", null, null);
+ mRequest.setSequence(1);
+ secondRequest.setSequence(2);
+ mDispatcher.processRequest(mRequest);
+ mDispatcher.processRequest(secondRequest);
+ verify(mNetworkQueue).put(mRequest);
+ verifyNoResponse(mDelivery);
+ }
+
+ @Test
+ public void tripleCacheMiss_networkErrorOnFirst() throws Exception {
+ StringRequest secondRequest =
+ new StringRequest(Request.Method.GET, "http://foo", null, null);
+ StringRequest thirdRequest =
+ new StringRequest(Request.Method.GET, "http://foo", null, null);
+ mRequest.setSequence(1);
+ secondRequest.setSequence(2);
+ thirdRequest.setSequence(3);
+ mDispatcher.processRequest(mRequest);
+ mDispatcher.processRequest(secondRequest);
+ mDispatcher.processRequest(thirdRequest);
+
+ verify(mNetworkQueue).put(mRequest);
+ verifyNoResponse(mDelivery);
+
+ ((Request<?>) mRequest).notifyListenerResponseNotUsable();
+ // Second request should now be in network queue.
+ verify(mNetworkQueue).put(secondRequest);
+ // Another unusable response, third request should now be added.
+ ((Request<?>) secondRequest).notifyListenerResponseNotUsable();
+ verify(mNetworkQueue).put(thirdRequest);
+ }
+
+ @Test
+ public void duplicateSoftExpiredCacheHit_failedRequest() throws Exception {
+ Cache.Entry entry = CacheTestUtils.makeRandomCacheEntry(null, false, true);
+ when(mCache.get(anyString())).thenReturn(entry);
+
+ StringRequest secondRequest =
+ new StringRequest(Request.Method.GET, "http://foo", null, null);
+ mRequest.setSequence(1);
+ secondRequest.setSequence(2);
+
+ mDispatcher.processRequest(mRequest);
+ mDispatcher.processRequest(secondRequest);
+
+ // Soft expiration needs to use the deferred Runnable variant of postResponse,
+ // so make sure it gets to run.
+ ArgumentCaptor<Runnable> runnable = ArgumentCaptor.forClass(Runnable.class);
+ verify(mDelivery).postResponse(any(Request.class), any(Response.class), runnable.capture());
+ runnable.getValue().run();
+ // This way we can verify the behavior of the Runnable as well.
+
+ verify(mNetworkQueue).put(mRequest);
+ verify(mDelivery)
+ .postResponse(any(Request.class), any(Response.class), any(Runnable.class));
+
+ ((Request<?>) mRequest).notifyListenerResponseNotUsable();
+ // Second request should now be in network queue.
+ verify(mNetworkQueue).put(secondRequest);
+ }
+
+ @Test
+ public void duplicateSoftExpiredCacheHit_successfulRequest() throws Exception {
+ Cache.Entry entry = CacheTestUtils.makeRandomCacheEntry(null, false, true);
+ when(mCache.get(anyString())).thenReturn(entry);
+
+ StringRequest secondRequest =
+ new StringRequest(Request.Method.GET, "http://foo", null, null);
+ mRequest.setSequence(1);
+ secondRequest.setSequence(2);
+
+ mDispatcher.processRequest(mRequest);
+ mDispatcher.processRequest(secondRequest);
+
+ // Soft expiration needs to use the deferred Runnable variant of postResponse,
+ // so make sure it gets to run.
+ ArgumentCaptor<Runnable> runnable = ArgumentCaptor.forClass(Runnable.class);
+ verify(mDelivery).postResponse(any(Request.class), any(Response.class), runnable.capture());
+ runnable.getValue().run();
+ // This way we can verify the behavior of the Runnable as well.
+
+ verify(mNetworkQueue).put(mRequest);
+ verify(mDelivery)
+ .postResponse(any(Request.class), any(Response.class), any(Runnable.class));
+
+ ((Request<?>) mRequest).notifyListenerResponseReceived(Response.success(null, entry));
+ // Second request should have delivered response.
+ verify(mNetworkQueue, never()).put(secondRequest);
+ verify(mDelivery)
+ .postResponse(any(Request.class), any(Response.class), any(Runnable.class));
+ }
+
+ @Test
+ public void processRequestNotifiesListener() throws Exception {
+ RequestQueue.RequestEventListener listener = mock(RequestQueue.RequestEventListener.class);
+ RequestQueue queue = new RequestQueue(mCache, mNetwork, 0, mDelivery);
+ queue.addRequestEventListener(listener);
+ mRequest.setRequestQueue(queue);
+
+ Cache.Entry entry = CacheTestUtils.makeRandomCacheEntry(null, false, false);
+ when(mCache.get(anyString())).thenReturn(entry);
+ mDispatcher.processRequest(mRequest);
+
+ InOrder inOrder = inOrder(listener);
+ inOrder.verify(listener)
+ .onRequestEvent(mRequest, RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_STARTED);
+ inOrder.verify(listener)
+ .onRequestEvent(mRequest, RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_FINISHED);
+ inOrder.verifyNoMoreInteractions();
+ }
+}