Mocking Intents: The Parcelable Injection Pattern

Fel
3 min readSep 24, 2019

Warning: This article is about testing for “native Android development”. However, I do not recommend native Android development anymore. Instead, I recommend to use cross-platform frameworks like https://capacitorjs.com/ or https://flutter.dev/.

When testing Android apps, it may be needed to trigger specific actions with Mock-Intents. Beside of primitive data types, an Intent can carry so-called “Parcelable objects”. Parcelables implement an efficient low-level interface for serializing and deserializing the object.
In many cases, Parcelables are system-specific objects that are provided by the Android SDK.

Mocking Parcelables

When mocking an Intent, it can be necessary to mock Parcelables that should be bundled with the Intent. However, some Parcelables are difficult to mock due to Android SDK restrictions.
In those cases, libraries like Mockito can come in handy.
Unfortunately, Mockito does not play well with Parcelables.
A major problem is that a Mock-Parcelable is not a mock object anymore after serializing and deserializing. Therefore, the deserialized objects loses all its stubbed method implementation as well as the ability to spy on method calls. In other words, serializing and deserializing an object prevents us from using many Mockito features.

Android P and Private Fields

Another problem is that Parcelables are picky about private fields. If some private field is set to null, then this may trigger a NullpointerException when serializing the Parcelable.
To make matters worse, Android P and later prevent access to private SDK fields via reflection. A reason is that Google wants to make the Android ecosystem more stable and forward-compatible. However, since private fields are needed to serialize Parcelables, it is hard to mock Parcelables for Android P.

The Parcelable Injection Pattern

By applying static dependency injection, we are able to mock Parcelables and circumvent the above-mentioned limitations. This hack involves overwriting a static final field via Java reflection. Specifically, we overwrite the CREATE object that is used to deserialize Parcelables. This becomes clear with a short code snippet. The following Kotlin code demonstrates how to inject an android.nfc.Tag object into an app under test.

// Step 1: Mock some Parcelable, whatever you want.
val mockTag = mockNfcTag()
// Step 2: Construct a "parcelable creator" that returns the mock object.
val mockParcelCreator = object : Parcelable.Creator<Tag> {
override fun createFromParcel(`in`: Parcel): Tag {
// This closure holds a reference to mockTag.
return mockTag
}
override fun newArray(size: Int): Array<Tag> {
return arrayOf(mockTag) // Second closure.
}
}
// Step 3: Reassign the static final field android.nfc.Tag.CREATOR, which installs mockParcelCreator globally.
reassignStaticFinalField(android.nfc.Tag::class.java.getField(“CREATOR”), mockParcelCreator)

Afterwards, you can fire an Intent to launch an activity under test. If the activity under test attempts to deserialize a Parcelable of the same type, then it receives the previously injected mock object.

Reassigning Static Final Fields In Android

Here is the implementation of reassignStaticFinalField()`:

fun reassignStaticFinalField(field: Field, newValue: Any?) {
field.isAccessible = true
val modifiersField = Field::class.java.getDeclaredField(“accessFlags”)
modifiersField.isAccessible = true
modifiersField.setInt(field, field.modifiers and Modifier.FINAL.inv())
field.set(null, newValue)
}

For Android P and later, this only works for public static final fields. In our case, this is not an issue since the parcelable creator is a public field. Prior to Android P, it was also possible to reassign private fields.

Usage Warning

The Parcelable Injection Pattern is a dirty hack that is not recommended for widespread usage. If you can change the code under test, then this hack is pretty much useless. In most cases, it is better to rely on conventional proxy design patterns for injecting test objects.

--

--