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
dependencies {
testCompile 'org.nanohttpd:nanohttpd:2.3.1'
}
Simple MockHttpServer
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
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
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.
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
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);
}