Testing HTTP URL Fetcher Response Handling in Glide Library
This test suite validates the HTTP URL fetching functionality in Glide’s image loading library, focusing on server response handling and URL redirection scenarios. It verifies proper handling of HTTP status codes, redirects, timeouts, and header management.
Test Coverage Overview
Implementation Analysis
Technical Details
Best Practices Demonstrated
bumptech/glide
library/test/src/test/java/com/bumptech/glide/load/data/HttpUrlFetcherServerTest.java
package com.bumptech.glide.load.data;
import static com.bumptech.glide.RobolectricConstants.ROBOLECTRIC_SDK;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.bumptech.glide.Priority;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.Headers;
import com.bumptech.glide.testutil.TestUtil;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
/**
* Tests {@link com.bumptech.glide.load.data.HttpUrlFetcher} against server responses. Tests for
* behavior (connection/disconnection/options) should go in {@link
* com.bumptech.glide.load.data.HttpUrlFetcherTest}, response handling should go here.
*/
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE, sdk = ROBOLECTRIC_SDK)
public class HttpUrlFetcherServerTest {
private static final String DEFAULT_PATH = "/fakepath";
private static final int TIMEOUT_TIME_MS = 300;
@Mock private DataFetcher.DataCallback<InputStream> callback;
private MockWebServer mockWebServer;
private boolean defaultFollowRedirects;
private ArgumentCaptor<InputStream> streamCaptor;
@Before
public void setUp() throws IOException {
MockitoAnnotations.initMocks(this);
defaultFollowRedirects = HttpURLConnection.getFollowRedirects();
HttpURLConnection.setFollowRedirects(false);
mockWebServer = new MockWebServer();
mockWebServer.start();
streamCaptor = ArgumentCaptor.forClass(InputStream.class);
}
@After
public void tearDown() throws IOException {
HttpURLConnection.setFollowRedirects(defaultFollowRedirects);
mockWebServer.shutdown();
}
@Test
public void testReturnsInputStreamOnStatusOk() throws Exception {
String expected = "fakedata";
mockWebServer.enqueue(new MockResponse().setBody(expected).setResponseCode(200));
HttpUrlFetcher fetcher = getFetcher();
fetcher.loadData(Priority.HIGH, callback);
verify(callback).onDataReady(streamCaptor.capture());
TestUtil.assertStreamOf(expected, streamCaptor.getValue());
assertThat(mockWebServer.takeRequest().getMethod()).isEqualTo("GET");
}
@Test
public void testHandlesRedirect301s() throws Exception {
String expected = "fakedata";
mockWebServer.enqueue(
new MockResponse()
.setResponseCode(301)
.setHeader("Location", mockWebServer.url("/redirect").toString()));
mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(expected));
getFetcher().loadData(Priority.LOW, callback);
verify(callback).onDataReady(streamCaptor.capture());
TestUtil.assertStreamOf(expected, streamCaptor.getValue());
assertThat(mockWebServer.takeRequest().getMethod()).isEqualTo("GET");
assertThat(mockWebServer.takeRequest().getMethod()).isEqualTo("GET");
}
@Test
public void testHandlesRedirect302s() throws Exception {
String expected = "fakedata";
mockWebServer.enqueue(
new MockResponse()
.setResponseCode(302)
.setHeader("Location", mockWebServer.url("/redirect").toString()));
mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(expected));
getFetcher().loadData(Priority.LOW, callback);
verify(callback).onDataReady(streamCaptor.capture());
TestUtil.assertStreamOf(expected, streamCaptor.getValue());
assertThat(mockWebServer.takeRequest().getMethod()).isEqualTo("GET");
assertThat(mockWebServer.takeRequest().getMethod()).isEqualTo("GET");
}
@Test
public void testHandlesRelativeRedirects() throws Exception {
String expected = "fakedata";
mockWebServer.enqueue(
new MockResponse().setResponseCode(301).setHeader("Location", "/redirect"));
mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(expected));
getFetcher().loadData(Priority.NORMAL, callback);
verify(callback).onDataReady(streamCaptor.capture());
TestUtil.assertStreamOf(expected, streamCaptor.getValue());
RecordedRequest first = mockWebServer.takeRequest();
assertThat(first.getMethod()).isEqualTo("GET");
RecordedRequest second = mockWebServer.takeRequest();
assertThat(second.getPath()).endsWith("/redirect");
assertThat(second.getMethod()).isEqualTo("GET");
}
@Test
public void testHandlesUpToFiveRedirects() throws Exception {
int numRedirects = 4;
String expected = "redirectedData";
String redirectBase = "/redirect";
for (int i = 0; i < numRedirects; i++) {
mockWebServer.enqueue(
new MockResponse()
.setResponseCode(301)
.setHeader("Location", mockWebServer.url(redirectBase + i).toString()));
}
mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(expected));
getFetcher().loadData(Priority.NORMAL, callback);
verify(callback).onDataReady(streamCaptor.capture());
TestUtil.assertStreamOf(expected, streamCaptor.getValue());
RecordedRequest request = mockWebServer.takeRequest();
assertThat(request.getPath()).contains(DEFAULT_PATH);
assertThat(request.getMethod()).isEqualTo("GET");
for (int i = 0; i < numRedirects; i++) {
RecordedRequest current = mockWebServer.takeRequest();
assertThat(current.getPath()).contains(redirectBase + i);
assertThat(current.getMethod()).isEqualTo("GET");
}
}
@Test
public void testFailsOnRedirectLoops() throws Exception {
mockWebServer.enqueue(
new MockResponse()
.setResponseCode(301)
.setHeader("Location", mockWebServer.url("/redirect").toString()));
mockWebServer.enqueue(
new MockResponse()
.setResponseCode(301)
.setHeader("Location", mockWebServer.url("/redirect").toString()));
getFetcher().loadData(Priority.IMMEDIATE, callback);
verify(callback).onLoadFailed(isA(IOException.class));
}
@Test
public void testFailsIfRedirectLocationIsNotPresent() throws Exception {
mockWebServer.enqueue(new MockResponse().setResponseCode(301));
getFetcher().loadData(Priority.NORMAL, callback);
verify(callback).onLoadFailed(isA(IOException.class));
}
@Test
public void testFailsIfRedirectLocationIsPresentAndEmpty() throws Exception {
mockWebServer.enqueue(new MockResponse().setResponseCode(301).setHeader("Location", ""));
getFetcher().loadData(Priority.NORMAL, callback);
verify(callback).onLoadFailed(isA(IOException.class));
}
@Test
public void testFailsIfStatusCodeIsNegativeOne() throws Exception {
mockWebServer.enqueue(new MockResponse().setResponseCode(-1));
getFetcher().loadData(Priority.LOW, callback);
verify(callback).onLoadFailed(isA(IOException.class));
}
@Test
public void testFailsAfterTooManyRedirects() throws Exception {
for (int i = 0; i < 10; i++) {
mockWebServer.enqueue(
new MockResponse()
.setResponseCode(301)
.setHeader("Location", mockWebServer.url("/redirect" + i).toString()));
}
getFetcher().loadData(Priority.NORMAL, callback);
verify(callback).onLoadFailed(isA(IOException.class));
}
@Test
public void testFailsIfStatusCodeIs500() throws Exception {
mockWebServer.enqueue(new MockResponse().setResponseCode(500));
getFetcher().loadData(Priority.NORMAL, callback);
verify(callback).onLoadFailed(isA(IOException.class));
}
@Test
public void testFailsIfStatusCodeIs400() throws Exception {
mockWebServer.enqueue(new MockResponse().setResponseCode(400));
getFetcher().loadData(Priority.LOW, callback);
verify(callback).onLoadFailed(isA(IOException.class));
}
@Test
public void testSetsReadTimeout() throws Exception {
MockWebServer tempWebServer = new MockWebServer();
tempWebServer.enqueue(
new MockResponse().setBody("test").throttleBody(1, TIMEOUT_TIME_MS, TimeUnit.MILLISECONDS));
tempWebServer.start();
try {
getFetcher().loadData(Priority.HIGH, callback);
} finally {
tempWebServer.shutdown();
// shutdown() called before any enqueue() blocks until it times out.
mockWebServer.enqueue(new MockResponse().setResponseCode(200));
}
verify(callback).onLoadFailed(isA(IOException.class));
}
@Test
public void testAppliesHeadersInGlideUrl() throws Exception {
mockWebServer.enqueue(new MockResponse().setResponseCode(200));
String headerField = "field";
String headerValue = "value";
Map<String, String> headersMap = new HashMap<>();
headersMap.put(headerField, headerValue);
Headers headers = mock(Headers.class);
when(headers.getHeaders()).thenReturn(headersMap);
getFetcher(headers).loadData(Priority.HIGH, callback);
assertThat(mockWebServer.takeRequest().getHeader(headerField)).isEqualTo(headerValue);
}
private HttpUrlFetcher getFetcher() {
return getFetcher(Headers.DEFAULT);
}
private HttpUrlFetcher getFetcher(Headers headers) {
URL url = mockWebServer.url(DEFAULT_PATH).url();
return new HttpUrlFetcher(
new GlideUrl(url, headers), TIMEOUT_TIME_MS, HttpUrlFetcher.DEFAULT_CONNECTION_FACTORY);
}
}