Tuesday, January 18, 2011

How to test HTTP requests

Many, if not most, Android applications communicate with remote servers via the HTTP protocol. The Android SDK provides facilities for accomplishing this by including the Apache HttpClient library, and Robolectric instruments this library in order to make it easier to write test code for interactions that occur over HTTP.

In most cases the remote calls that your application makes will cause some kind of work to be done that cannot be done directly on the phone. Perhaps your application will cause money to be transferred between bank accounts, an email to be sent to all of your friends, or maybe it will cause your microwave to start cooking dinner for you. During the process of developing and testing your application these calls will be made thousands of times, often incorrectly. In order to prevent application development from draining your bank account, alienating your friends and burning down your home, Robolectric prevents these calls from actually being made, and instead keeps a record of them and provides a couple of different mechanisms for simulating their results.

The following is a portion of the HttpTest class taken from the RobolectricSample application:

@RunWith(RobolectricTestRunner.class)
public class HttpTest {
    @Test
    public void testGet_FormsCorrectRequest_noBasicAuth()
            throws Exception {
        Robolectric.addPendingHttpResponse(200, "OK");

        new Http().get(
            "www.example.com",
            Maps.<String, String>newHashMap(),
            null,
            null);

        assertThat(
            ((HttpUriRequest) Robolectric.getSentHttpRequest(0)).getURI(),
            equalTo(URI.create("www.example.com")));
    }
}

This simple test demonstrates how to set up a simulated HttpResponse, make an HTTP GET request, and then retrieve and examine the contents of that request. In real life the call to new Http().get(... would actually be a call into application code that you expect to make an HTTP request, and the utility of this test would be to ensure that the request you were expecting was made and made correctly.

The call to Robolectric.addPendingResponse() is needed because in this scenario Robolectric is acting in the role of a mock server and it needs to know how to respond to the HTTP request in order for the test to proceed, if it doesn't then it throws a RuntimeException. In this test we are checking the request set up and would rather not have to set up the response which we don't care about. Robolectric allows us to set up a default response that can be shared among tests:

@RunWith(RobolectricTestRunner.class)
public class HttpTest {
    @Before
    public void setup() {
        Robolectric.setDefaultHttpResponse(200, "OK");
    }
    
    ...
}

Now tests that don't set up their own pending responses will get the default response.

8 comments:

  1. I tried adapting the above to my project but I'm seeing the dreaded "Stub!" error when my class under test (a Service that I instantiate from the test code by new MyService()) tries to create a DefaultHttpClient. i.e. at this line:

    HttpClient httpclient = new DefaultHttpClient();

    Any tips on troubleshooting the setup?

    ReplyDelete
  2. I see the same thing: new DefaultHttpClient() causes Stub! in AbstractHttpClient. and DefaultHttpClient., and then at my statement.

    This is regardless of whether I try to bindShadowClass to shadow those classes or not.

    Robolectric is first in Eclipse's build path.

    ReplyDelete
  3. Have you annotated your test class?



    @RunWith(RobolectricTestRunner.class)
    public class HttpTest {
    ....

    ReplyDelete
  4. My app uses HttoUrlConnection, nd i m trying to use roboelectric to unit test it. Roboelectric seem to work fine with the defaultHttpClient, bt if i use HttpUrlConnection i get indexOutOfBound Exception. it does not fetch the request.
    Thanks in advance..

    ReplyDelete
  5. I've got the same problem as Guneet, I've moved to using HttpUrlConnection rather than defaultHttpConnection as recommended by Google and now my previously working tests fail as Robolectric doesn't seem to support it. Is this connection type support in the pipeline?

    ReplyDelete
  6. This blog describes how to mock Apache HTTP client. However, Android recommends to use HttpURLConnection instead.

    How do you mock HttpUrlConnection, or more specifically, what should you do instead of new URL(url)?

    ReplyDelete
  7. Thankfully, this 'feature' can be disabled: Robolectric.getFakeHttpLayer().interceptHttpRequests(false);

    ReplyDelete
  8. These suggestions also served like the good way to realize that other individuals have the same dreams the same as mine to figure out significantly more in respect of this problem. Check is raeli jewellery for best Jewellery.

    ReplyDelete