|
|
Unit Testing Network Calls
|
|
|
|
|
|
### Using NanoHttpd to isolate network call testing
|
|
|
|
|
|
Setup
|
|
|
#### Gradle
|
|
|
if you are only using nanohttp for testing then use testCompile, otherwise compile
|
|
|
```gradle
|
|
|
dependencies {
|
|
|
testCompile 'org.nanohttpd:nanohttpd:2.3.1'
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### Simple MockHttpServer
|
|
|
```java
|
|
|
class MockHttpServer extends NanoHTTPD {
|
|
|
|
|
|
private Context mContext;
|
|
|
private Context mTestContext;
|
|
|
static private Object initlock= new Object();
|
|
|
|
|
|
/**
|
|
|
* Constructs an HTTP server on given port.
|
|
|
*
|
|
|
* @param port
|
|
|
*/
|
|
|
public MockHttpServer( Context context, int port ) {
|
|
|
super(port);
|
|
|
mContext = context;
|
|
|
mTestContext = null;
|
|
|
}
|
|
|
|
|
|
public MockHttpServer(Context context, Context testContext, int port) {
|
|
|
super(port);
|
|
|
mContext = context.getApplicationContext();
|
|
|
mTestContext = testContext;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public Response serve(IHTTPSession session) {
|
|
|
String route = session.getUri();
|
|
|
List<String> segs = Uri.parse(route).getPathSegments();
|
|
|
JSONObject jsonBody = new JSONObject();
|
|
|
switch(segs.get(0)){
|
|
|
case "asset":
|
|
|
if (segs.size()<2 ) {
|
|
|
|
|
|
String asset = segs.get(1);
|
|
|
try {
|
|
|
|
|
|
return newChunkedResponse(
|
|
|
Response.Status.OK,
|
|
|
"application/octet-stream",
|
|
|
mContext.getAssets().open(asset)
|
|
|
);
|
|
|
|
|
|
} catch (IOException e) {
|
|
|
String responseContents = null;
|
|
|
responseContents = "<html><body><h1>Request Error</h1>\n";
|
|
|
responseContents += "<p>Uri: " + session.getUri() + " </p>";
|
|
|
responseContents += "<p>Message: " + e.getMessage() + " </p>";
|
|
|
return newFixedLengthResponse(responseContents);
|
|
|
}
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case "echo":
|
|
|
String msg = segs.size()>1 ? segs.get(1) : "";
|
|
|
return newFixedLengthResponse(msg);
|
|
|
//return newFixedLengthResponse(Response.Status.OK, "plain/text", , segs.get(1).length() );
|
|
|
|
|
|
case "delay":
|
|
|
return super.serve(session);
|
|
|
|
|
|
case "get":
|
|
|
default:
|
|
|
}
|
|
|
// 404: Not Found error
|
|
|
return super.serve(session);
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### Unit test setup
|
|
|
```java
|
|
|
public class HttpUrlConnectionNetworkTests extends AndroidTestCase {
|
|
|
NanoHTTPD httpServer;
|
|
|
Network network;
|
|
|
|
|
|
@Override
|
|
|
public void setUp() throws Exception {
|
|
|
super.setUp();
|
|
|
RenamingDelegatingContext context = new RenamingDelegatingContext(getContext(), "test_");
|
|
|
|
|
|
mTestContext = InstrumentationRegistry.getContext();
|
|
|
|
|
|
httpServer = new MockHttpServer(getContext(), mTestContext, 7078);
|
|
|
httpServer.start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
|
|
|
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public void tearDown() throws Exception {
|
|
|
|
|
|
httpServer.stop();
|
|
|
super.tearDown();
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### Simple synchronous network call
|
|
|
```java
|
|
|
public void testSimpleEcho() throws Exception {
|
|
|
URL url = new URL("http://localhost:7078/echo/hello_world");
|
|
|
String responseContents = "";
|
|
|
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
|
|
|
try {
|
|
|
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
|
|
|
java.util.Scanner s = new java.util.Scanner(in).useDelimiter("\\A");
|
|
|
if (s.hasNext()) {
|
|
|
responseContents = s.next();
|
|
|
}
|
|
|
} finally {
|
|
|
urlConnection.disconnect();
|
|
|
}
|
|
|
assertEquals("hello_world", responseContents );
|
|
|
}
|
|
|
```
|
|
|
|
|
|
|
|
|
### Using a Semaphore to test network responses
|
|
|
Semaphore is an easy way to deal with async calls. A semaphore tracks a permit count, release( ) increments the permit count, aquire( ) decrements the permit if it was non zero.
|
|
|
|
|
|
The pattern is to initialize the Semaphore with no permits.
|
|
|
after the async call.. wait with tryAcquire() for a timeout period
|
|
|
When the listener is called, it releases a permit and tryAcquire() will return.
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
public void testStringRequest() throws Exception {
|
|
|
final Semaphore semaphore = new Semaphore(0);
|
|
|
URL url = new URL("http://localhost:7078/echo/hello_world");
|
|
|
StringRequest stringRequest = new StringRequest(Request.Method.GET, url.toString(),
|
|
|
new Response.Listener<String>() {
|
|
|
@Override
|
|
|
public void onResponse(String response) {
|
|
|
assertNotNull(response);
|
|
|
assertEquals("hello_world", response);
|
|
|
semaphore.release();
|
|
|
}
|
|
|
}, new Response.ErrorListener() {
|
|
|
@Override
|
|
|
public void onErrorResponse(VolleyError error) {
|
|
|
}
|
|
|
});
|
|
|
|
|
|
stringRequest.setShouldCache(false);
|
|
|
BTNetVolley.with(getContext())
|
|
|
.getRequestQueue()
|
|
|
.add(stringRequest);
|
|
|
|
|
|
assertTrue(semaphore.tryAcquire(10000, TimeUnit.MILLISECONDS));
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
### Using Future<> to test network requests
|
|
|
Volley supports getting a Future<> object which can be used to inline a network for unit testing
|
|
|
```java
|
|
|
public void testFutureStringRequest() throws Exception {
|
|
|
URL url = new URL("http://localhost:7078/echo/hello_world");
|
|
|
final RequestFuture<String> futureRequest = RequestFuture.newFuture();
|
|
|
StringRequest stringRequest = new StringRequest(Request.Method.GET, url.toString(),futureRequest, futureRequest);
|
|
|
|
|
|
stringRequest.setShouldCache(false);
|
|
|
BTNetVolley.with(getContext())
|
|
|
.getRequestQueue()
|
|
|
.add(stringRequest);
|
|
|
|
|
|
String result = futureRequest.get(5, TimeUnit.SECONDS);
|
|
|
assertNotNull(result);
|
|
|
assertEquals("hello_world", result);
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|