diff options
Diffstat (limited to 'core/src/test/java/com/android/volley/CacheDispatcherTest.java')
-rw-r--r-- | core/src/test/java/com/android/volley/CacheDispatcherTest.java | 276 |
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(); + } +} |