From 58f9c05b9ad88512bd4da9d8e27df2e834a06b38 Mon Sep 17 00:00:00 2001 From: Brett Chabot Date: Thu, 20 Nov 2014 13:14:44 -0800 Subject: Add a RequestFinishedListener to RequestQueue. Change-Id: I51a1beacea4e448e71b7e9c3ec580165d799994a --- .../volley/RequestQueueIntegrationTest.java | 207 +++++++++++++++++++++ .../java/com/android/volley/RequestQueueTest.java | 181 +++--------------- .../com/android/volley/mock/ShadowSystemClock.java | 11 ++ 3 files changed, 242 insertions(+), 157 deletions(-) create mode 100644 src/test/java/com/android/volley/RequestQueueIntegrationTest.java create mode 100644 src/test/java/com/android/volley/mock/ShadowSystemClock.java (limited to 'src/test/java') diff --git a/src/test/java/com/android/volley/RequestQueueIntegrationTest.java b/src/test/java/com/android/volley/RequestQueueIntegrationTest.java new file mode 100644 index 0000000..a73435c --- /dev/null +++ b/src/test/java/com/android/volley/RequestQueueIntegrationTest.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2015 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 com.android.volley.Request.Priority; +import com.android.volley.RequestQueue.RequestFinishedListener; +import com.android.volley.mock.MockRequest; +import com.android.volley.mock.ShadowSystemClock; +import com.android.volley.toolbox.NoCache; +import com.android.volley.utils.ImmediateResponseDelivery; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; +import static org.mockito.MockitoAnnotations.initMocks; + + +/** + * Integration tests for {@link RequestQueue}, that verify its behavior in conjunction with real dispatcher, queues and + * Requests. Network is mocked out + */ +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowSystemClock.class}) +public class RequestQueueIntegrationTest { + + private ResponseDelivery mDelivery; + @Mock private Network mMockNetwork; + + @Before public void setUp() throws Exception { + mDelivery = new ImmediateResponseDelivery(); + initMocks(this); + } + + @Test public void add_requestProcessedInCorrectOrder() throws Exception { + // Enqueue 2 requests with different cache keys, and different priorities. The second, higher priority request + // takes 20ms. + // Assert that first request is only handled after the first one has been parsed and delivered. + MockRequest lowerPriorityReq = new MockRequest(); + MockRequest higherPriorityReq = new MockRequest(); + lowerPriorityReq.setCacheKey("1"); + higherPriorityReq.setCacheKey("2"); + lowerPriorityReq.setPriority(Priority.LOW); + higherPriorityReq.setPriority(Priority.HIGH); + + RequestFinishedListener listener = mock(RequestFinishedListener.class); + Answer delayAnswer = new Answer() { + @Override + public NetworkResponse answer(InvocationOnMock invocationOnMock) throws Throwable { + Thread.sleep(20); + return mock(NetworkResponse.class); + } + }; + //delay only for higher request + when(mMockNetwork.performRequest(higherPriorityReq)).thenAnswer(delayAnswer); + when(mMockNetwork.performRequest(lowerPriorityReq)).thenReturn(mock(NetworkResponse.class)); + + RequestQueue queue = new RequestQueue(new NoCache(), mMockNetwork, 1, mDelivery); + queue.addRequestFinishedListener(listener); + queue.add(lowerPriorityReq); + queue.add(higherPriorityReq); + queue.start(); + + // you cannot do strict order verification in combination with timeouts with mockito 1.9.5 :( + // as an alternative, first verify no requests have finished, while higherPriorityReq should be processing + verifyNoMoreInteractions(listener); + // verify higherPriorityReq goes through first + verify(listener, timeout(100)).onRequestFinished(higherPriorityReq); + // verify lowerPriorityReq goes last + verify(listener, timeout(10)).onRequestFinished(lowerPriorityReq); + queue.stop(); + } + + /** + * Asserts that requests with same cache key are processed in order. + * + * Needs to be an integration test because relies on complex interations between various queues + */ + @Test public void add_dedupeByCacheKey() throws Exception { + // Enqueue 2 requests with the same cache key. The first request takes 20ms. Assert that the + // second request is only handled after the first one has been parsed and delivered. + Request req1 = new MockRequest(); + Request req2 = new MockRequest(); + RequestFinishedListener listener = mock(RequestFinishedListener.class); + Answer delayAnswer = new Answer() { + @Override + public NetworkResponse answer(InvocationOnMock invocationOnMock) throws Throwable { + Thread.sleep(20); + return mock(NetworkResponse.class); + } + }; + //delay only for first + when(mMockNetwork.performRequest(req1)).thenAnswer(delayAnswer); + when(mMockNetwork.performRequest(req2)).thenReturn(mock(NetworkResponse.class)); + + RequestQueue queue = new RequestQueue(new NoCache(), mMockNetwork, 3, mDelivery); + queue.addRequestFinishedListener(listener); + queue.add(req1); + queue.add(req2); + queue.start(); + + // you cannot do strict order verification with mockito 1.9.5 :( + // as an alternative, first verify no requests have finished, then verify req1 goes through + verifyNoMoreInteractions(listener); + verify(listener, timeout(100)).onRequestFinished(req1); + verify(listener, timeout(10)).onRequestFinished(req2); + queue.stop(); + } + + /** + * Verify RequestFinishedListeners are informed when requests are canceled + * + * Needs to be an integration test because relies on Request -> dispatcher -> RequestQueue interaction + */ + @Test public void add_requestFinishedListenerCanceled() throws Exception { + RequestFinishedListener listener = mock(RequestFinishedListener.class); + Request request = new MockRequest(); + Answer delayAnswer = new Answer() { + @Override + public NetworkResponse answer(InvocationOnMock invocationOnMock) throws Throwable { + Thread.sleep(200); + return mock(NetworkResponse.class); + } + }; + RequestQueue queue = new RequestQueue(new NoCache(), mMockNetwork, 1, mDelivery); + + when(mMockNetwork.performRequest(request)).thenAnswer(delayAnswer); + + queue.addRequestFinishedListener(listener); + queue.start(); + queue.add(request); + + request.cancel(); + verify(listener, timeout(100)).onRequestFinished(request); + queue.stop(); + } + + /** + * Verify RequestFinishedListeners are informed when requests are successfully delivered + * + * Needs to be an integration test because relies on Request -> dispatcher -> RequestQueue interaction + */ + @Test public void add_requestFinishedListenerSuccess() throws Exception { + NetworkResponse response = mock(NetworkResponse.class); + Request request = new MockRequest(); + RequestFinishedListener listener = mock(RequestFinishedListener.class); + RequestFinishedListener listener2 = mock(RequestFinishedListener.class); + RequestQueue queue = new RequestQueue(new NoCache(), mMockNetwork, 1, mDelivery); + + queue.addRequestFinishedListener(listener); + queue.addRequestFinishedListener(listener2); + queue.start(); + queue.add(request); + + verify(listener, timeout(100)).onRequestFinished(request); + verify(listener2, timeout(100)).onRequestFinished(request); + + queue.stop(); + } + + /** + * Verify RequestFinishedListeners are informed when request errors + * + * Needs to be an integration test because relies on Request -> dispatcher -> RequestQueue interaction + */ + @Test public void add_requestFinishedListenerError() throws Exception { + RequestFinishedListener listener = mock(RequestFinishedListener.class); + Request request = new MockRequest(); + RequestQueue queue = new RequestQueue(new NoCache(), mMockNetwork, 1, mDelivery); + + when(mMockNetwork.performRequest(request)).thenThrow(new VolleyError()); + + queue.addRequestFinishedListener(listener); + queue.start(); + queue.add(request); + + verify(listener, timeout(100)).onRequestFinished(request); + queue.stop(); + } + +} diff --git a/src/test/java/com/android/volley/RequestQueueTest.java b/src/test/java/com/android/volley/RequestQueueTest.java index cc88d3b..bcf3ff2 100644 --- a/src/test/java/com/android/volley/RequestQueueTest.java +++ b/src/test/java/com/android/volley/RequestQueueTest.java @@ -16,114 +16,47 @@ package com.android.volley; -import com.android.volley.Request.Priority; -import com.android.volley.mock.MockNetwork; -import com.android.volley.mock.MockRequest; +import com.android.volley.mock.ShadowSystemClock; import com.android.volley.toolbox.NoCache; -import com.android.volley.utils.CacheTestUtils; import com.android.volley.utils.ImmediateResponseDelivery; - -import android.os.SystemClock; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import org.robolectric.annotation.Implements; - -import java.util.ArrayList; -import java.util.List; -import java.util.Random; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.*; +import static org.mockito.Mockito.*; +import static org.mockito.MockitoAnnotations.initMocks; -// TODO: Resurrect these tests when we have something like a finish() observer. -// They are really gross as-is and depend on a bunch of sleeping and whatnot. -@Ignore +/** + * Unit tests for RequestQueue, with all dependencies mocked out + */ @RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowSystemClock.class}) public class RequestQueueTest { + private ResponseDelivery mDelivery; + @Mock private Network mMockNetwork; @Before public void setUp() throws Exception { mDelivery = new ImmediateResponseDelivery(); - } - - /** - * Make a list of requests with random priorities. - * @param count Number of requests to make - */ - private List makeRequests(int count) { - Request.Priority[] allPriorities = Request.Priority.values(); - Random random = new Random(); - - List requests = new ArrayList(); - for (int i = 0; i < count; i++) { - MockRequest request = new MockRequest(); - Request.Priority priority = allPriorities[random.nextInt(allPriorities.length)]; - request.setCacheKey(String.valueOf(i)); - request.setPriority(priority); - requests.add(request); - } - return requests; - } - - @Test public void add_requestProcessedInCorrectOrder() throws Exception { - int requestsToMake = 100; - - OrderCheckingNetwork network = new OrderCheckingNetwork(); - RequestQueue queue = new RequestQueue(new NoCache(), network, 1, mDelivery); - - for (Request request : makeRequests(requestsToMake)) { - queue.add(request); - } - - network.setExpectedRequests(requestsToMake); - queue.start(); - network.waitUntilExpectedDone(2000); // 2 seconds - queue.stop(); - } - - @Test public void add_dedupeByCacheKey() throws Exception { - OrderCheckingNetwork network = new OrderCheckingNetwork(); - final AtomicInteger parsed = new AtomicInteger(); - final AtomicInteger delivered = new AtomicInteger(); - // Enqueue 2 requests with the same cache key. The first request takes 1.5s. Assert that the - // second request is only handled after the first one has been parsed and delivered. - DelayedRequest req1 = new DelayedRequest(1500, parsed, delivered); - DelayedRequest req2 = new DelayedRequest(0, parsed, delivered) { - @Override - protected Response parseNetworkResponse(NetworkResponse response) { - assertEquals(1, parsed.get()); // req1 must have been parsed. - assertEquals(1, delivered.get()); // req1 must have been parsed. - return super.parseNetworkResponse(response); - } - }; - network.setExpectedRequests(2); - RequestQueue queue = new RequestQueue(new NoCache(), network, 3, mDelivery); - queue.add(req1); - queue.add(req2); - queue.start(); - network.waitUntilExpectedDone(2000); - queue.stop(); + initMocks(this); } @Test public void cancelAll_onlyCorrectTag() throws Exception { - MockNetwork network = new MockNetwork(); - RequestQueue queue = new RequestQueue(new NoCache(), network, 3, mDelivery); + RequestQueue queue = new RequestQueue(new NoCache(), mMockNetwork, 0, mDelivery); Object tagA = new Object(); Object tagB = new Object(); - MockRequest req1 = new MockRequest(); - req1.setTag(tagA); - MockRequest req2 = new MockRequest(); - req2.setTag(tagB); - MockRequest req3 = new MockRequest(); - req3.setTag(tagA); - MockRequest req4 = new MockRequest(); - req4.setTag(tagA); + Request req1 = mock(Request.class); + when(req1.getTag()).thenReturn(tagA); + Request req2 = mock(Request.class); + when(req2.getTag()).thenReturn(tagB); + Request req3 = mock(Request.class); + when(req3.getTag()).thenReturn(tagA); + Request req4 = mock(Request.class); + when(req4.getTag()).thenReturn(tagA); queue.add(req1); // A queue.add(req2); // B @@ -131,75 +64,9 @@ public class RequestQueueTest { queue.cancelAll(tagA); queue.add(req4); // A - assertTrue(req1.cancel_called); // A cancelled - assertFalse(req2.cancel_called); // B not cancelled - assertTrue(req3.cancel_called); // A cancelled - assertFalse(req4.cancel_called); // A added after cancel not cancelled + verify(req1).cancel(); // A cancelled + verify(req3).cancel(); // A cancelled + verify(req2, never()).cancel(); // B not cancelled + verify(req4, never()).cancel(); // A added after cancel not cancelled } - - private class OrderCheckingNetwork implements Network { - private Priority mLastPriority = Priority.IMMEDIATE; - private int mLastSequence = -1; - private Semaphore mSemaphore; - - public void setExpectedRequests(int expectedRequests) { - // Leave one permit available so the waiter can find it. - expectedRequests--; - mSemaphore = new Semaphore(-expectedRequests); - } - - public void waitUntilExpectedDone(long timeout) - throws InterruptedException, TimeoutError { - if (mSemaphore.tryAcquire(timeout, TimeUnit.MILLISECONDS) == false) { - throw new TimeoutError(); - } - } - - @Override - public NetworkResponse performRequest(Request request) { - Priority thisPriority = request.getPriority(); - int thisSequence = request.getSequence(); - - int priorityDiff = thisPriority.compareTo(mLastPriority); - - // Should never experience a higher priority after a lower priority - assertFalse(priorityDiff > 0); - - // If we're not transitioning to a new priority block, check sequence numbers - if (priorityDiff == 0) { - assertTrue(thisSequence > mLastSequence); - } - mLastSequence = thisSequence; - mLastPriority = thisPriority; - - mSemaphore.release(); - return new NetworkResponse(new byte[16]); - } - } - - private class DelayedRequest extends Request { - private final long mDelayMillis; - private final AtomicInteger mParsedCount; - private final AtomicInteger mDeliveredCount; - - public DelayedRequest(long delayMillis, AtomicInteger parsed, AtomicInteger delivered) { - super(Request.Method.GET, "http://buganizer/", null); - mDelayMillis = delayMillis; - mParsedCount = parsed; - mDeliveredCount = delivered; - } - - @Override - protected Response parseNetworkResponse(NetworkResponse response) { - mParsedCount.incrementAndGet(); - SystemClock.sleep(mDelayMillis); - return Response.success(new Object(), CacheTestUtils.makeRandomCacheEntry(null)); - } - - @Override - protected void deliverResponse(Object response) { - mDeliveredCount.incrementAndGet(); - } - } - } diff --git a/src/test/java/com/android/volley/mock/ShadowSystemClock.java b/src/test/java/com/android/volley/mock/ShadowSystemClock.java new file mode 100644 index 0000000..f2697cc --- /dev/null +++ b/src/test/java/com/android/volley/mock/ShadowSystemClock.java @@ -0,0 +1,11 @@ +package com.android.volley.mock; + +import android.os.SystemClock; +import org.robolectric.annotation.Implements; + +@Implements(value = SystemClock.class, callThroughByDefault = true) +public class ShadowSystemClock { + public static long elapsedRealtime() { + return 0; + } +} -- cgit v1.2.3