Reputation: 3880
How do you unit test Parcelable? I created a Parcelable class, and wrote this unit test
TestClass test = new TestClass();
Bundle bundle = new Bundle();
bundle.putParcelable("test", test);
TestClass testAfter = bundle.getParcelable("test");
assertEquals(testAfter.getStuff(), event1.getStuff());
I purposely try to fail the test by returning null in the createFromParcel()
, but it seems to succeed. It looks like it doesn't get parceled until it's needed. How do I force the Bundle to..bundle?
Upvotes: 43
Views: 21620
Reputation: 1779
I'm using a parcel helper class to convert an object to/from parcel.
See this gist: https://gist.github.com/scallacs/f749a7385bcf75829a98d7b651efd02e
Usage is very simple:
Model model = new Model("HelloWorld");
// Create an object from the parcel model
Model createdFromParcel = ParcelTestHelper.createFromParcel(model, model.CREATOR);
// Do your assertions...
assertEquals(model.value, createdFromParcel.value);
The implementation:
import android.os.Parcel;
import android.os.Parcelable;
public class ParcelTestHelper {
public static String DEFAULT_CREATOR_FIELD = "CREATOR";
public static <T extends Parcelable> T createFromParcel(T input, Parcelable.Creator<T> creator) {
Parcel parcel = toParcel(input);
return fromParcel(parcel, creator);
}
public static <T extends Parcelable> T createFromParcel(T input) throws NoSuchFieldException, IllegalAccessException {
return createFromParcel(input, getCreator(input));
}
public static <T extends Parcelable> Parcel toParcel(T input) {
Parcel parcel = Parcel.obtain();
input.writeToParcel(parcel, input.describeContents());
parcel.setDataPosition(0);
return parcel;
}
public static <T> Parcelable.Creator<T> getCreator(T input) throws NoSuchFieldException, IllegalAccessException {
return getCreator(input, DEFAULT_CREATOR_FIELD);
}
public static <T> Parcelable.Creator<T> getCreator(T input, String field) throws NoSuchFieldException, IllegalAccessException {
Object creator = input.getClass().getField(field).get(input);
if (!(creator instanceof Parcelable.Creator)) {
throw new InternalError("Should have field " + field + " instance of Parcelable.Creator");
}
return (Parcelable.Creator<T>) creator;
}
public static <T extends Parcelable> T fromParcel(Parcel parcel, Parcelable.Creator<T> creator) {
return creator.createFromParcel(parcel);
}
}
Upvotes: 0
Reputation: 312
Since this question and answers helped me several years down the line, I thought I'd add my own suggestion, which would be to assert
that the dataPosition()
at the end of the read was the same as at the end of the write. Building on Xilconic's answer:
@Test
public void testTestClassParcelable(){
TestClass test = new TestClass();
// Obtain a Parcel object and write the parcelable object to it:
Parcel parcel = Parcel.obtain();
test.writeToParcel(parcel, 0);
//>>>>> Record dataPosition
int eop = parcel.dataPosition();
// After you're done with writing, you need to reset the parcel for reading:
parcel.setDataPosition(0);
// Reconstruct object from parcel and asserts:
TestClass createdFromParcel = TestClass.CREATOR.createFromParcel(parcel);
assertEquals(test, createdFromParcel);
//>>>>> Verify dataPosition
assertEquals(eop, parcel.dataPosition());
}
Background: This came to me after spending an (embarrassing) amount of time debugging a bad Parcelable
. In my case, writeToParcel
was writing a duplicate field from one object within a moderately complex object graph. Therefore subsequent objects were read incorrectly, giving misleading exceptions, and no errors on tests with the specific misbehaving object.
It was a pain to track down, and then I realized checking dataPosition
would have pinpointed the problem faster since I have tests on inner objects as well as the main container.
Kotlin: Since I am working in Kotlin, a little lambda and reifying magic:
class ParcelWrap<T>(val value: T)
val <T> T.parcel: ParcelWrap<T> get() = ParcelWrap(this)
inline fun <reified T: Parcelable> ParcelWrap<T>.test(
flags: Int = 0,
classLoader: ClassLoader = T::class.java.classLoader,
checks: (T) -> Unit
): T {
// Create the parcel
val parcel: Parcel = Parcel.obtain()
parcel.writeParcelable(this.value, flags)
// Record dataPosition
val eop = parcel.dataPosition()
// Reset the parcel
parcel.setDataPosition(0)
// Read from the parcel
val newObject = parcel.readParcelable<T>(classLoader)
// Perform the checks provided in the lambda
checks(newObject)
// Verify dataPosition
assertEquals("writeToParcel wrote too much data or read didn't finish reading", eop, parcel.dataPosition())
return newObject
}
I can now test very easily along these lines, if there is a complete and reliable equals()
:
testObject.parcel.test { assertEquals(testObject, it) }
Note the .parcel.test
avoids having to re-specify generic type parameter using this answer.
Or, for more complex assertions:
testObject.parcel.test {
assertEquals(123, it.id)
assertEquals("dewey", it.name)
// ...
}
Upvotes: 7
Reputation: 3845
I've found this link showing how you can unit test a parcelable object: http://stuffikeepforgettinghowtodo.blogspot.nl/2009/02/unit-test-your-custom-parcelable.html
You can actually skip the Bundle
if you don't really need to include it like zorch did his suggestion. You would then get something like this:
public void testTestClassParcelable(){
TestClass test = new TestClass();
// Obtain a Parcel object and write the parcelable object to it:
Parcel parcel = Parcel.obtain();
test.writeToParcel(parcel, 0);
// After you're done with writing, you need to reset the parcel for reading:
parcel.setDataPosition(0);
// Reconstruct object from parcel and asserts:
TestClass createdFromParcel = TestClass.CREATOR.createFromParcel(parcel);
assertEquals(test, createdFromParcel);
}
Upvotes: 102
Reputation: 745
You can do it by this way:
//Create parcelable object and put to Bundle
Question q = new Question(questionId, surveyServerId, title, type, answers);
Bundle b = new Bundle();
b.putParcelable("someTag", q);
//Save bundle to parcel
Parcel parcel = Parcel.obtain();
b.writeToParcel(parcel, 0);
//Extract bundle from parcel
parcel.setDataPosition(0);
Bundle b2 = parcel.readBundle();
b2.setClassLoader(Question.class.getClassLoader());
Question q2 = b2.getParcelable("someTag");
//Check that objects are not same and test that objects are equal
assertFalse("Bundle is the same", b2 == b);
assertFalse("Question is the same", q2 == q);
assertTrue("Questions aren't equal", q2.equals(q));
Upvotes: 19