Reputation:
I’m trying to launch Activity
in Espresso. The thing is I want to put mocked extras to the Intent
I’m using to launch the Activity
. Here is the example.
@RunWith(AndroidJUnit4.class)
public final class NiceActivityTester
{
@Rule
public final ActivityTestRule<NiceActivity> activityRule = new ActivityTestRule<>(NiceActivity.class, true, false);
@Test
public void justStartPlease() {
NiceThing niceThing = Mockito.mock(NiceThing.class);
Mockito.when(niceThing.getName()).thenReturn("Nice!");
Intent intent = new Intent(InstrumentationRegistry.getTargetContext(), NiceActivity.class);
intent.putExtra("NICE_THING", niceThing);
activityRule.launchActivity(intent);
}
}
Unfortunately unmarshalling Parcelable
fails.
java.lang.RuntimeException: Unable to start activity ComponentInfo{app.application/app.application.activity.NiceActivity}: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: NiceThing_Proxy
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2325)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2387)
at android.app.ActivityThread.access$800(ActivityThread.java:151)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5254)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
Caused by: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: NiceThing_Proxy
at android.os.Parcel.readParcelableCreator(Parcel.java:2295)
at android.os.Parcel.readParcelable(Parcel.java:2245)
at android.os.Parcel.readValue(Parcel.java:2152)
at android.os.Parcel.readArrayMapInternal(Parcel.java:2485)
at android.os.BaseBundle.unparcel(BaseBundle.java:221)
at android.os.Bundle.getParcelable(Bundle.java:755)
at android.content.Intent.getParcelableExtra(Intent.java:5088)
at app.application.NiceActivity.getNiceThing(NiceActivity.java:40)
at app.application.NiceActivity.setUpToolbar(NiceActivity.java:30)
at app.application.NiceActivity.onCreate(NiceActivity.java:20)
at android.app.Activity.performCreate(Activity.java:5990)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1106)
at android.support.test.runner.MonitoringInstrumentation.callActivityOnCreate(MonitoringInstrumentation.java:534)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2278)
Is there any way to use mocked extras with Intent
? Maybe some best practices regarding this area?
I really don’t want to make constructors for every model I’m using, some of these classes are quite complex with a dozen of fields. Spying on the extra or the Intent
using Mockito doesn’t help.
Upvotes: 9
Views: 8598
Reputation: 118
In recent updates, ActivityTestRule
was declared a deprecated class. The Android Developers documentation recommends using ActivityScenario
or ActivityScenarioRule
.
Assuming that we are using ActivityScenarioRule
and your test code as the base example, the solution to include an Intent
would then be as follows:
@RunWith(AndroidJUnit4.class)
public final class NiceActivityTester
{
@Rule
public final ActivityScenarioRule<NiceActivity> scenarioRule =
new ActivityScenarioRule<>(
new Intent(
InstrumentationRegistry.getInstrumentation().getTargetContext(),
NiceActivity.class)
.putExtra("your_extra1_key", yourExtra1Value) // first extra
.putExtra("your_extra2_key", yourExtra2Value)); // second extra
.putExtra("your_extra3_key", createParcelableObject())); // parcelable extra
// you may add more
// An example function that creates and returns a parcelable object
ObjectType createParcelableObject() {
ObjectType parcelableObject = new ObjectType();
parcelableObject.setAttribute1("attribute1");
parcelableObject.setAttribute2("attribute2");
parcelableObject.setAttribute3("attribute3");
.
. // more attributes setting, for example
.
return parcelableObject;
}
@Test
public void justStartPlease() {
ActivityScenarioRule<NiceActivity> = scenarioRule.getScenario();
// The rest would be your test code
}
}
Here are a few things that you might want to take note of:
getTargetContext
, or technically, the InstrumentRegistry
class itself, is now deprecated. The documentation recommends using ApplicationProvider
. Therefore, instead of InstrumentationRegistry.getInstrumentation().getTargetContext()
, you may replace with ApplicationProvider.getApplicationContext()
. Either way, I have tested and verified that both work at the time of this answer's posting.ActivityScenarioRule
, you are able to supply an Intent
as its argument. Hence, you can include extras when launching your Activity
. Simply create a new Intent
and include the Activity
that you intend to launch like the code sample above.ActivityScenarioRule
class/object. In the example code above, a function has been written to create and return a parcelable object while creating the ActivityScenarioRule
. The documentation specifically mentions that the class "launches a given activity before the test starts and closes after the test". So, whatever it is that you need to include for your activity, do it when you're instantiating your ActivityScenarioRule
.Upvotes: 0
Reputation: 2729
Kotlin solution for this:
@get:Rule
val mActivityTestRule: ActivityTestRule<TheActivity> =
object : ActivityTestRule<TheActivity>(TheActivity::class.java) {
override fun getActivityIntent(): Intent {
val targetContext = InstrumentationRegistry.getInstrumentation().targetContext
return Intent(targetContext, TheActivity::class.java).apply {
putExtra(TheActivity.ACTIVITY_TITLE,
targetContext.resources.getString(R.string.the_text))
putExtra(TheActivity.SETTING_TITLE,
targetContext.resources.getString(R.string.the_title))
putExtra(TheActivity.SCREEN_TYPE,
TheActivity.ScreenType.AMOUNT.name)
}
}
}
Upvotes: 8
Reputation: 139
Use ActivityTestRule
and override getActivityIntent
Example found here
public class MainActivityLaunchIntentTest {
@Rule
public ActivityTestRule<MainActivity> mActivityRule =
new ActivityTestRule<MainActivity>(MainActivity.class) {
@Override
protected Intent getActivityIntent() {
Context targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
Intent result = new Intent(targetContext, MainActivity.class);
result.putExtra("Name", "Earth");
return result;
}
};
@Test
public void shouldShowHelloEarth() {
onView(withId(R.id.main__tv_hello)).check(matches(withText("Hello Earth!")));
}
}
Upvotes: 12