Sunday, February 8, 2009

Unit Test Your Custom Parcelable

Your Android application needs to pass some custom data between processes in an Intent. You will be calling Intent.putExtra(String, Parcelable). It would be easier to test if the Parcel class where not final with only private constructors. That keeps us from testing the methods individually. We are forced to perform only round trip testing -- write our Parcelable to a Parcel, then call the creator with the same Parcel instance.

There is one important step right in the middle of the round trip. The Parcel needs to be reset to be ready for read. Think of this just like working with java.nio.ByteBuffer. With ByteBuffer, when you are done writing, you call flip. With Parcel, when you want to read -- call setDataPosition(0). Here is a sample test.

I'll decoreate a Person object as a ParcelablePerson that implements the Parcelable interface and has the requisite public static final CREATOR. Following TDD I've only created enough of the implementation code below to make the test compile.
    public void testPersonTakesRoundTripThroughParcel() throws Exception {
Person testPerson = new Person();
ParcelablePerson testObject = new ParcelablePerson(testPerson);
Parcel parcel = Parcel.obtain();
testObject.writeToParcel(parcel, 0);
//done writing, now reset parcel for reading
parcel.setDataPosition(0);
//finish round trip
ParcelablePerson createFromParcel = ParcelablePerson.CREATOR.createFromParcel(parcel);

assertEquals(testPerson, createFromParcel.getPerson());
}
public class Person {
...
}
public class ParcelablePerson implements Parcelable {
private Person person;
public ParcelablePerson(Person person) {
this.person = person;
}

public Person getPerson() {
return person;
}

@Override
public int describeContents() {
return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
//call dest.write... methods
}

public static final Creator CREATOR = new Creator() {
@Override
public ParcelablePerson createFromParcel(Parcel source) {
return null;
}

@Override
public ParcelablePerson[] newArray(int size) {
return null;
}};
}

4 comments:

ssaidwho said...

Thanks! Making some classes Parcelable now, this was handy to test them.

Felix said...

This is wrong. Since wirting a parcel will write the class name first the alignment gets messed up by one if you use the CREATOR directly.

Rather use:

ParcelableObject copy = parcel.readParcelable(this.getClass().getClassLoader());

ngirardin said...

Wow, this is really an useful post, thank you very much! :)

Adolfo said...

Your post saved me a lot of time. Thank you :)