Test startActivity with implicit Intent failure - android

I'm trying to find a way to test the failure scenario for startActivity with implicit intent.
So I have an intent like this:
val intent = Intent(Intent.ACTION_VIEW, uri)
And I want to test the scenario where startActivity(intent) will throw ActivityNotFoundException.
I've used Intents.intending() for stubbing the success scenario, but that does not have a way to test this kind of scenario.
Other option that I've tried was with a dummy Activity and creating a spy on that - but this has the big disadvantage that I need to add that activity to my Manifest (which I want to avoid).
Any ideas on how this can be tested without affecting production code?

Related

How to inspect an intent to see if it was an intent that started/launched the app?

How to inspect an intent to see if it was an intent that started/launched the app versus an intent that was used to navigate from within the app or once the app was already opened?
We have special UI handling based on if the intent was launched using a deeplink that doesn't work well if the app is launched from a deeplink. Right now, I am adding an extra boolean to the intent to workaround the issue we have, but was wondering if there was something available in the framework/SDK to determine if a particular intent was the app start/launch intent.
NOTE: We are using jetpack navigation to handle incoming intents/deeplinks and to route deeplinks within the app. That is why in the snippet below you can see I am accessing the intent from currentBackStackEntry
Here is the code I've put in to support it so far:
In MainActivity onCreate
intent.putExtra(KEY_IS_APP_CREATED_INTENT, true)
In our UI logic code
val isAppCreationIntent = navController.currentBackStackEntry?.arguments
?.parcelable<Intent>(KEY_DEEP_LINK_INTENT)
?.getBooleanExtra(KEY_IS_APP_CREATED_INTENT, false) ?: false
I logged a bug/feature request to provide this capability here:
https://issuetracker.google.com/u/1/issues/265981498

How intents and startActvity works internally?

Everyone knows if you create intent to start another activity, you pass in as a parameter into startActivity. But I just thought about the possible scenario: intent says system "call this activity", the system sees manifest and then runs activity, or this running acts internally in the app, something like "call some method of some class"?
Probably a stupid question, but I couldn't find enough info. So how does it works?
Following is the way how intent communication works:
Activity A creates an Intent with an action description and passes it to startActivity().
The Android System searches all apps for an intent filter that matches the intent. When a match is found,
the system starts the matching activity (Activity B) by invoking its onCreate() method and passing it the Intent.!

Clear Intents between instrumentation tests

One of my Espresso tests is apparently failing because a broadcast intent triggered by the previous test is arriving part way through the subsequent test. At this point, the application is in an inconsistent state and missing some SharedPreferences which are expected by the intent handler.
Is there a way to make sure that all waiting intents have been processed or cancelled before starting an Espresso test?
I don't think there is a way to clear intents that are already dispatched. However you can get around this by tweaking your "previous test".
If you want to fully take advantage of Espresso you have to reduce the scope of your test as small as possible. So instead of broadcasting an intent and verifying what happens next, try stubbing your intents.
Use IntentsTestRule instead of ActivityTestRule and stub out all internal intents (sometimes even the external ones) using the below code:
intending(isInternal()).respondWith(new Instrumentation.ActivityResult(Activity.RESULT_OK, null));
Now in your "previous test", check if the right intent is getting broadcasted using something like:
intended(hasComponent("com.example.something.SomeActivity"));
This way, you can test your functionality without actually broadcasting any intents. I always stub my Internal and External intents and verify if the right intent is getting triggered in all my tests. This way the next activity doesn't get launched and my Espresso tests are faster and stable. Always ensure to test only what you are want to test, and keep the scope really small.
Read more: https://google.github.io/android-testing-support-library/docs/espresso/intents/
I had the same problem testing two intents,
one to share text and another to share audio.
I managed to do it by calling between the intents.
Intents.init()
//
Intents.release()
I don't know if it is the right way but is working on the question.
Let say I have to check two Intents with action CHOOSER.
Even my intents are ACTION_SEND, I have a CHOOSER in my code to send intent.
Intents.init()
testIntent1
Intents.release()
Intents.init()
testIntent2
Intents.release()
testIntent1() {
Matcher<Intent> expectedIntent = getIntentMatcherChooser();
String textInET = "share this text";
replaceTextCloseKeyboard(R.id.et_main, text);
openDrawerClickItem(R.id.nav_item_share_text);
intended(expectedIntent);
}
getIntentMatcherChooser(){
Matcher<Intent> expectedIntent = allOf(hasAction(Intent.ACTION_CHOOSER));
intending(expectedIntent).respondWith(new Instrumentation.ActivityResult(0, null));
}

How can I check the expected intent sent without actually launching activity in Espresso?

I have a UI test which clicks a button, and then launch a new Activity in its onClickListener. The test checks whether expected intent is sent or not.
My problem is, I want to test whether expected intent is sent without actually launching the activity. Because I found that new activity initializes its state, and it makes subsequent tests flaky.
I know there are two Espresso Intents API, which are intended and intending, but both fail to meet my needs. intended API actually launches the target activity, and intending API doesn't launch the activity, but it calls onActivityResult callback which I don't want either. Because I'm afraid that code inside onActivityResult may cause another flakiness.
Also intending doesn't assert whether matching intent is sent. It just calls onActivityResult callback when matching intent is found, which means I have to check whether onActivityResult is called or not!
Is there a clean way to achieve what I want?
If you want to test whether expected intent is sent without actually launching the activity you can do it by capturing the intent with an activityResult and then catching the activity :
Intent intent = new Intent();
ActivityResult intentResult = new ActivityResult(Activity.RESULT_OK,intent);
intending(anyIntent()).respondWith(intentResult);
onView(withId(R.id.view_id_to_perform_clicking)).check(matches(isDisplayed())).perform(click());
intended(allOf(hasComponent(ActivityToBeOpened.class.getName())));
This would catch any attempt of launching ActivityToBeOpened. If you want to be more specific you can also catch an intent with Extras:
intended(allOf(hasComponent(ActivityToBeOpened.class.getName()), hasExtra("paramName", "value")));
Hope that helps.
Espresso's Intents class is a concise and handy api, but when it doesn't meet your needs, there is an alternative. If you use AndroidJUnit4 test runner, you can get Instrumentaion instance using InstrumentationRegistry.getInstrumentation(), and then you can add Instrumentation.ActivityMonitor instance.
Instrumentation.ActivityMonitor am = new Instrumentation.ActivityMonitor("YOUR_ACTIVITY", null, true);
InstrumentationRegistry.getInstrumentation().addMonitor(am);
onView(withId(R.id.view_id_to_perform_clicking)).check(matches(isDisplayed())).perform(click());
assertTrue(InstrumentationRegistry.getInstrumentation().checkMonitorHit(am, 1));
The third parameter of ActivityMonitor constructor tells we want to block activity launching. Note that this approach has its limitation. In contrast to Espresso Intents' rich Matcher support, You can not set multiple condition for ActivityMonitor.
You can find several samples in ApiDemos, especially in ContactsSelectInstrumentation class.
Actually, you can block any intent to launch an external or your own activity but still use the rich Espresso Intents API:
Instrumentation.ActivityMonitor soloMonitor = solo.getActivityMonitor();
instrumentation.removeMonitor(soloMonitor);
IntentFilter filter = null;
// Block any intent
Instrumentation.ActivityMonitor monitor = instrumentation.addMonitor(filter, null, true);
instrumentation.addMonitor(soloMonitor);
// User action that results in an external browser activity being launched.
user.clickOnView(system.getView(R.id.callButton));
instrumentation.waitForIdleSync();
Intents.intended(Matchers.allOf(
IntentMatchers.hasAction(Matchers.equalTo(Intent.ACTION_VIEW)),
IntentMatchers.hasData(Matchers.equalTo(Uri.parse(url))),
IntentMatchers.toPackage(chromePackage)));
instrumentation.removeMonitor(monitor);
You able to do that because Espresso Intents still records every Intent with IntentMonitor callback even if you block them. Look at the source code of Espresso Intents on how they do that.
If you use Robotium Solo framework you need to move your own ActivityMonitor before their one. Otherwise just skip the lines related to this.

Open my application from another in android

My boss asked me to prove that my application behaves properly when summoned by another application (dunno why he asked that).
So I have two apps here, one launches a second one. How I launch the specific app I want? Using Intent launch seemly any generic app that reaches a certain goal, not the app I really want.
Give this a try.
Intent secondIntent = new Intent();
secondIntent.setAction(Intent.ACTION_MAIN);
secondIntent.setClassName("com.example", "com.example.YourSecondApp");
startActivity(secondIntent);
I should point out that com.example should be the package of your second application (the one you want to call) and com.example.YourSecondapp is the class name where you have your onCreate() method.
Intent secondApp = new Intent("com.test.SecondApp");
startActivity(secondApp);
Check out for more examples
http://developer.android.com/resources/faq/commontasks.html#opennewscreen
Create one Intent using the following code
Explicit Intent
When you know the particular component(activity/service) to be loaded
Intent intent = new Intent();
intent.setClass("className/package name");
start<Activity/Service>(intent);
Imlicit Intent
When we do not have the idea which class to load and we know the Action to be perform by the launched application we can go with this intent.
Action needs to set, and the Android run time fallows the intent Resolution technique and list out(one or more components) the components to perform the action. from the list out components (if more than one), user will get the chance to launch his chosen application

Categories

Resources