1 /* 2 * Copyright (C) 2011 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.squareup.okhttp.mockwebserver; 17 18 import com.squareup.okhttp.Headers; 19 import java.io.BufferedReader; 20 import java.io.IOException; 21 import java.io.InputStream; 22 import java.io.InputStreamReader; 23 import java.io.OutputStream; 24 import java.net.ConnectException; 25 import java.net.HttpURLConnection; 26 import java.net.ProtocolException; 27 import java.net.SocketTimeoutException; 28 import java.net.URL; 29 import java.net.URLConnection; 30 import java.util.ArrayList; 31 import java.util.Arrays; 32 import java.util.List; 33 import java.util.concurrent.TimeUnit; 34 import java.util.concurrent.atomic.AtomicBoolean; 35 import org.junit.After; 36 import org.junit.Rule; 37 import org.junit.Test; 38 import org.junit.runner.Description; 39 import org.junit.runners.model.Statement; 40 41 import static java.util.concurrent.TimeUnit.NANOSECONDS; 42 import static java.util.concurrent.TimeUnit.SECONDS; 43 import static org.junit.Assert.assertEquals; 44 import static org.junit.Assert.assertNotNull; 45 import static org.junit.Assert.assertTrue; 46 import static org.junit.Assert.fail; 47 48 public final class MockWebServerTest { 49 @Rule public final MockWebServer server = new MockWebServer(); 50 defaultMockResponse()51 @Test public void defaultMockResponse() { 52 MockResponse response = new MockResponse(); 53 assertEquals(Arrays.asList("Content-Length: 0"), headersToList(response)); 54 assertEquals("HTTP/1.1 200 OK", response.getStatus()); 55 } 56 setBodyAdjustsHeaders()57 @Test public void setBodyAdjustsHeaders() throws IOException { 58 MockResponse response = new MockResponse().setBody("ABC"); 59 assertEquals(Arrays.asList("Content-Length: 3"), headersToList(response)); 60 assertEquals("ABC", response.getBody().readUtf8()); 61 assertEquals("HTTP/1.1 200 OK", response.getStatus()); 62 } 63 mockResponseAddHeader()64 @Test public void mockResponseAddHeader() { 65 MockResponse response = new MockResponse() 66 .clearHeaders() 67 .addHeader("Cookie: s=square") 68 .addHeader("Cookie", "a=android"); 69 assertEquals(Arrays.asList("Cookie: s=square", "Cookie: a=android"), headersToList(response)); 70 } 71 mockResponseSetHeader()72 @Test public void mockResponseSetHeader() { 73 MockResponse response = new MockResponse() 74 .clearHeaders() 75 .addHeader("Cookie: s=square") 76 .addHeader("Cookie: a=android") 77 .addHeader("Cookies: delicious"); 78 response.setHeader("cookie", "r=robot"); 79 assertEquals(Arrays.asList("Cookies: delicious", "cookie: r=robot"), headersToList(response)); 80 } 81 regularResponse()82 @Test public void regularResponse() throws Exception { 83 server.enqueue(new MockResponse().setBody("hello world")); 84 85 URL url = server.getUrl("/"); 86 HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 87 connection.setRequestProperty("Accept-Language", "en-US"); 88 InputStream in = connection.getInputStream(); 89 BufferedReader reader = new BufferedReader(new InputStreamReader(in)); 90 assertEquals(HttpURLConnection.HTTP_OK, connection.getResponseCode()); 91 assertEquals("hello world", reader.readLine()); 92 93 RecordedRequest request = server.takeRequest(); 94 assertEquals("GET / HTTP/1.1", request.getRequestLine()); 95 assertEquals("en-US", request.getHeader("Accept-Language")); 96 } 97 redirect()98 @Test public void redirect() throws Exception { 99 server.enqueue(new MockResponse() 100 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 101 .addHeader("Location: " + server.getUrl("/new-path")) 102 .setBody("This page has moved!")); 103 server.enqueue(new MockResponse().setBody("This is the new location!")); 104 105 URLConnection connection = server.getUrl("/").openConnection(); 106 InputStream in = connection.getInputStream(); 107 BufferedReader reader = new BufferedReader(new InputStreamReader(in)); 108 assertEquals("This is the new location!", reader.readLine()); 109 110 RecordedRequest first = server.takeRequest(); 111 assertEquals("GET / HTTP/1.1", first.getRequestLine()); 112 RecordedRequest redirect = server.takeRequest(); 113 assertEquals("GET /new-path HTTP/1.1", redirect.getRequestLine()); 114 } 115 116 /** 117 * Test that MockWebServer blocks for a call to enqueue() if a request 118 * is made before a mock response is ready. 119 */ dispatchBlocksWaitingForEnqueue()120 @Test public void dispatchBlocksWaitingForEnqueue() throws Exception { 121 new Thread() { 122 @Override public void run() { 123 try { 124 Thread.sleep(1000); 125 } catch (InterruptedException ignored) { 126 } 127 server.enqueue(new MockResponse().setBody("enqueued in the background")); 128 } 129 }.start(); 130 131 URLConnection connection = server.getUrl("/").openConnection(); 132 InputStream in = connection.getInputStream(); 133 BufferedReader reader = new BufferedReader(new InputStreamReader(in)); 134 assertEquals("enqueued in the background", reader.readLine()); 135 } 136 nonHexadecimalChunkSize()137 @Test public void nonHexadecimalChunkSize() throws Exception { 138 server.enqueue(new MockResponse() 139 .setBody("G\r\nxxxxxxxxxxxxxxxx\r\n0\r\n\r\n") 140 .clearHeaders() 141 .addHeader("Transfer-encoding: chunked")); 142 143 URLConnection connection = server.getUrl("/").openConnection(); 144 InputStream in = connection.getInputStream(); 145 try { 146 in.read(); 147 fail(); 148 } catch (IOException expected) { 149 } 150 } 151 responseTimeout()152 @Test public void responseTimeout() throws Exception { 153 server.enqueue(new MockResponse() 154 .setBody("ABC") 155 .clearHeaders() 156 .addHeader("Content-Length: 4")); 157 server.enqueue(new MockResponse().setBody("DEF")); 158 159 URLConnection urlConnection = server.getUrl("/").openConnection(); 160 urlConnection.setReadTimeout(1000); 161 InputStream in = urlConnection.getInputStream(); 162 assertEquals('A', in.read()); 163 assertEquals('B', in.read()); 164 assertEquals('C', in.read()); 165 try { 166 in.read(); // if Content-Length was accurate, this would return -1 immediately 167 fail(); 168 } catch (SocketTimeoutException expected) { 169 } 170 171 URLConnection urlConnection2 = server.getUrl("/").openConnection(); 172 InputStream in2 = urlConnection2.getInputStream(); 173 assertEquals('D', in2.read()); 174 assertEquals('E', in2.read()); 175 assertEquals('F', in2.read()); 176 assertEquals(-1, in2.read()); 177 178 assertEquals(0, server.takeRequest().getSequenceNumber()); 179 assertEquals(0, server.takeRequest().getSequenceNumber()); 180 } 181 disconnectAtStart()182 @Test public void disconnectAtStart() throws Exception { 183 server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AT_START)); 184 server.enqueue(new MockResponse()); // The jdk's HttpUrlConnection is a bastard. 185 server.enqueue(new MockResponse()); 186 try { 187 server.getUrl("/a").openConnection().getInputStream(); 188 } catch (IOException expected) { 189 } 190 server.getUrl("/b").openConnection().getInputStream(); // Should succeed. 191 } 192 193 /** 194 * Throttle the request body by sleeping 500ms after every 3 bytes. With a 195 * 6-byte request, this should yield one sleep for a total delay of 500ms. 196 */ throttleRequest()197 @Test public void throttleRequest() throws Exception { 198 server.enqueue(new MockResponse() 199 .throttleBody(3, 500, TimeUnit.MILLISECONDS)); 200 201 long startNanos = System.nanoTime(); 202 URLConnection connection = server.getUrl("/").openConnection(); 203 connection.setDoOutput(true); 204 connection.getOutputStream().write("ABCDEF".getBytes("UTF-8")); 205 InputStream in = connection.getInputStream(); 206 assertEquals(-1, in.read()); 207 long elapsedNanos = System.nanoTime() - startNanos; 208 long elapsedMillis = NANOSECONDS.toMillis(elapsedNanos); 209 210 assertTrue(String.format("Request + Response: %sms", elapsedMillis), elapsedMillis >= 500); 211 assertTrue(String.format("Request + Response: %sms", elapsedMillis), elapsedMillis < 1000); 212 } 213 214 /** 215 * Throttle the response body by sleeping 500ms after every 3 bytes. With a 216 * 6-byte response, this should yield one sleep for a total delay of 500ms. 217 */ 218 @Test public void throttleResponse() throws Exception { 219 server.enqueue(new MockResponse() 220 .setBody("ABCDEF") 221 .throttleBody(3, 500, TimeUnit.MILLISECONDS)); 222 223 long startNanos = System.nanoTime(); 224 URLConnection connection = server.getUrl("/").openConnection(); 225 InputStream in = connection.getInputStream(); 226 assertEquals('A', in.read()); 227 assertEquals('B', in.read()); 228 assertEquals('C', in.read()); 229 assertEquals('D', in.read()); 230 assertEquals('E', in.read()); 231 assertEquals('F', in.read()); 232 assertEquals(-1, in.read()); 233 long elapsedNanos = System.nanoTime() - startNanos; 234 long elapsedMillis = NANOSECONDS.toMillis(elapsedNanos); 235 236 assertTrue(String.format("Request + Response: %sms", elapsedMillis), elapsedMillis >= 500); 237 assertTrue(String.format("Request + Response: %sms", elapsedMillis), elapsedMillis < 1000); 238 } 239 240 /** Delay the response body by sleeping 1s. */ 241 @Test public void delayResponse() throws IOException { 242 server.enqueue(new MockResponse() 243 .setBody("ABCDEF") 244 .setBodyDelay(1, SECONDS)); 245 246 long startNanos = System.nanoTime(); 247 URLConnection connection = server.getUrl("/").openConnection(); 248 InputStream in = connection.getInputStream(); 249 assertEquals('A', in.read()); 250 long elapsedNanos = System.nanoTime() - startNanos; 251 long elapsedMillis = NANOSECONDS.toMillis(elapsedNanos); 252 assertTrue(String.format("Request + Response: %sms", elapsedMillis), elapsedMillis >= 1000); 253 254 in.close(); 255 } 256 disconnectRequestHalfway()257 @Test public void disconnectRequestHalfway() throws IOException { 258 server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_DURING_REQUEST_BODY)); 259 // Limit the size of the request body that the server holds in memory to an arbitrary 260 // 3.5 MBytes so this test can pass on devices with little memory. 261 server.setBodyLimit(7 * 512 * 1024); 262 263 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 264 connection.setRequestMethod("POST"); 265 connection.setDoOutput(true); 266 connection.setFixedLengthStreamingMode(1024 * 1024 * 1024); // 1 GB 267 connection.connect(); 268 OutputStream out = connection.getOutputStream(); 269 270 byte[] data = new byte[1024 * 1024]; 271 int i; 272 for (i = 0; i < 1024; i++) { 273 try { 274 out.write(data); 275 out.flush(); 276 } catch (IOException e) { 277 break; 278 } 279 } 280 assertEquals(512f, i, 10f); // Halfway +/- 1% 281 } 282 disconnectResponseHalfway()283 @Test public void disconnectResponseHalfway() throws IOException { 284 server.enqueue(new MockResponse() 285 .setBody("ab") 286 .setSocketPolicy(SocketPolicy.DISCONNECT_DURING_RESPONSE_BODY)); 287 288 URLConnection connection = server.getUrl("/").openConnection(); 289 assertEquals(2, connection.getContentLength()); 290 InputStream in = connection.getInputStream(); 291 assertEquals('a', in.read()); 292 try { 293 int byteRead = in.read(); 294 // OpenJDK behavior: end of stream. 295 assertEquals(-1, byteRead); 296 } catch (ProtocolException e) { 297 // On Android, HttpURLConnection is implemented by OkHttp v2. OkHttp 298 // treats an incomplete response body as a ProtocolException. 299 } 300 } 301 headersToList(MockResponse response)302 private List<String> headersToList(MockResponse response) { 303 Headers headers = response.getHeaders(); 304 int size = headers.size(); 305 List<String> headerList = new ArrayList<>(size); 306 for (int i = 0; i < size; i++) { 307 headerList.add(headers.name(i) + ": " + headers.value(i)); 308 } 309 return headerList; 310 } 311 shutdownWithoutStart()312 @Test public void shutdownWithoutStart() throws IOException { 313 MockWebServer server = new MockWebServer(); 314 server.shutdown(); 315 } 316 shutdownWithoutEnqueue()317 @Test public void shutdownWithoutEnqueue() throws IOException { 318 MockWebServer server = new MockWebServer(); 319 server.start(); 320 server.shutdown(); 321 } 322 tearDown()323 @After public void tearDown() throws IOException { 324 server.shutdown(); 325 } 326 portImplicitlyStarts()327 @Test public void portImplicitlyStarts() throws IOException { 328 assertTrue(server.getPort() > 0); 329 } 330 hostNameImplicitlyStarts()331 @Test public void hostNameImplicitlyStarts() throws IOException { 332 assertNotNull(server.getHostName()); 333 } 334 toProxyAddressImplicitlyStarts()335 @Test public void toProxyAddressImplicitlyStarts() throws IOException { 336 assertNotNull(server.toProxyAddress()); 337 } 338 differentInstancesGetDifferentPorts()339 @Test public void differentInstancesGetDifferentPorts() throws IOException { 340 MockWebServer other = new MockWebServer(); 341 assertNotEquals(server.getPort(), other.getPort()); 342 other.shutdown(); 343 } 344 statementStartsAndStops()345 @Test public void statementStartsAndStops() throws Throwable { 346 final AtomicBoolean called = new AtomicBoolean(); 347 Statement statement = server.apply(new Statement() { 348 @Override public void evaluate() throws Throwable { 349 called.set(true); 350 server.getUrl("/").openConnection().connect(); 351 } 352 }, Description.EMPTY); 353 354 statement.evaluate(); 355 356 assertTrue(called.get()); 357 try { 358 server.getUrl("/").openConnection().connect(); 359 fail(); 360 } catch (ConnectException expected) { 361 } 362 } 363 364 // ANDROID-BEGIN Android uses JUnit 4.10 which does not have assertNotEquals() assertNotEquals(Object o1, Object o2)365 private static void assertNotEquals(Object o1, Object o2) { 366 org.junit.Assert.assertFalse(o1 == o2 || (o1 != null && o1.equals(o2))); 367 } 368 // ANDROID-END 369 } 370