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.

Tuesday, January 11, 2011

How to create your own Shadow Classes

You may find, from time to time, that Robolectric does not have the functionality to support a test that you want to write and you will want to add that functionality on your own. This article will show you how to write and extend Shadow classes to suit your needs and then register them with Robolectric so that they will be available to your tests.

The first step is to write the Shadow class. Here is a simple Shadow for the android Point class:

import android.graphics.Point;
import com.xtremelabs.robolectric.internal.Implementation;
import com.xtremelabs.robolectric.internal.Implements;
import com.xtremelabs.robolectric.internal.RealObject;

@Implements(Point.class)
public class ShadowPoint {
    @RealObject private Point realPoint;

    public void __constructor__(int x, int y) {
        realPoint.x = x;
        realPoint.y = y;
    }

    public void __constructor__(Point src) {
        realPoint.x = src.x;
        realPoint.y = src.y;
    }

    @Implementation
    public void set(int x, int y) {
        realPoint.x = x;
        realPoint.y = y;
    }

    @Implementation
    public final void offset(int dx, int dy) {
        realPoint.x += dx;
        realPoint.y += dy;
    }

    @Override @Implementation
    public String toString() {
        return "Point(" + realPoint.x + ", " + realPoint.y + ")";
    }
}

This example illustrates all of the most important aspects of writing Shadows. We start with an @Implements annotation that will tell Robolectric that our class is a Shadow for Android's Point class. This annotation, along with the @Implementation annotation (as shown on the set(), offset(), and toString() methods) are the bread and butter of writing Shadow classes. The @Implementation annotation tells Robolectric that the annotated method is meant to shadow the corresponding method with the same signature on the shadowed class.

Note that ShadowPoint doesn't extend Point. Robolectric creates an association between the two classes behind the scenes based on these annotations. Shadow classes mirror the inheritance hierarchy of the classes they shadow. So, for instance, the Shadow for TextView would be a sub-class of the Shadow for View. It is important to maintain this relationship so that inheritance will work properly within the Shadow class framework.

The @RealObject annotation is used to ask Robolectric to provide instances of the Shadow class with a reference to the object that they are shadowing. The annotated field must be of the same type as the class being shadowed and will automatically be populated by Robolectric when the class is instantiated.

While Robolectric provides control over the behavior of methods, it does not provide the same kind of control over access to member variables. So, when a class such as Point exposes its member variables (which, fortunately, is rare), its Shadow class should maintain the same behavior with respect to those member variables as the original class does. In our example we make ShadowPoints do this by getting and setting values on the fields of the shadowed Point object; the @RealObject annotation is the tool used to access the Point. (Normally a Shadow will store information about the state of the shadowed object in its own fields and ignore the private fields on the shadowed object.)

The @RealObject reference is also useful in any situation where the this reference would be used, such as when an object is expected to pass itself as a parameter to other methods, return a reference to itself, or polymorphically call other methods on itself. For instance, in the following example the onClick() method expects a parameter of type View , so the Shadow must pass a reference to the object it shadows rather than itself.

@Implements(View.class)
public class ShadowView {
    @RealObject protected View realView;

    @Implementation
    public boolean performClick() {
        if (onClickListener != null) {
            onClickListener.onClick(realView);
            return true;
        } else {
            return false;
        }
    }
}

Robolectric grants access to the constructors of shadowed objects by means of the special __constructor__() methods. Shadows themselves are always instantiated via their default constructors and their initial setup can be done at that time. Calls to the constructors of the shadowed class are diverted to the __constructor__() method with the corresponding signature, where the Shadow can perform any further initialization tasks.

If a Shadow needs to gain access to the Shadow of any other object (whether of the same or of a different class), it may use the Robolectric.shadowOf_() method. This may be useful if your Shadow needs to inquire about the state of another shadowed object in a way that the Android API does not normally expose.

Once the Shadow class has been written it must be registered with Robolectric in order to become available for use in your unit tests. This is done by the RobolectricTestRunner during test initialization.

If the new Shadow class is meant to be an addition to the standard set of Robolectric Shadows then it should be added to the list of shadow classes in the getDefaultShadowClasses() method of the com.xtremelabs.Robolectric class. In this case, it is convention to also add a shadowOf() method to the same class and specialize it for the new Shadow.

If you are creating the Shadow for local use, you will need to register it in the bindShadowClasses() method of the subclass of RobolectricTestRunner that you use in the @RunWith annotation on your tests:

public class CustomTestRunner extends RobolectricTestRunner {
    public CustomTestRunner(Class testClass)
            throws InitializationError {
        super(testClass);
    }

    @Override protected void bindShadowClasses() {
        Robolectric.bindShadowClass(ShadowPoint.class);
    }
}

It's also possible to register (or change) the Shadow for an individual test calling Robolectric.bindShadowClass() within the test or set-up method; just make sure you do that before any instances of the class are instantiated by the test.