Is there any good way to test the result code and data in an Android Espresso test? I am using Espresso 2.0.
Suppose I have an Activity called BarActivity.class, which upon performing some action, calls setResult(int resultCode, Intent data) with the appropriate payload.
I'd like to write a test case to verify the resultCode and data. However, because setResult() is a final method, I can't override it.
Some options I thought about were:
Define a new method like setActivityResult() and just use that so it can be intercepted, etc...
Write a test-only TestActivity that will call startActivityForResult() on BarActivity and check the result in TestActivity.onActivityResult()
Trying to think what's lesser of the two evils, or if there's any other suggestions on how to test for this. Any suggestions? Thanks!
If meanwhile you switched to the latest Espresso, version 3.0.1, you can simply use an ActivityTestRule and get the Activity result like this:
assertThat(rule.getActivityResult(), hasResultCode(Activity.RESULT_OK));
assertThat(rule.getActivityResult(), hasResultData(IntentMatchers.hasExtraWithKey(PickActivity.EXTRA_PICKED_NUMBER)));
You can find a working example here.
If you are willing to upgrade to 2.1, then take a look at Espresso-Intents:
Using the intending API (cousin of Mockito.when), you can provide a response for activities that are launched with startActivityForResult
This basically means it is possible to build and return any result when a specific activity is launched (in your case the BarActivity class).
Check this example here: https://google.github.io/android-testing-support-library/docs/espresso/intents/index.html#intent-stubbing
And also my answer on a somewhat similar issue (but with the contact picker activity), in which I show how to build a result and send it back to the Activity which called startActivityForResult()
this works for me:
#Test
fun testActivityForResult(){
// Build the result to return when the activity is launched.
val resultData = Intent()
resultData.putExtra(KEY_VALUE_TO_RETURN, true)
// Set up result stubbing when an intent sent to <ActivityB> is seen.
intending(hasComponent("com.xxx.xxxty.ActivityB")) //Path of <ActivityB>
.respondWith(
Instrumentation.ActivityResult(
RESULT_OK,
resultData
)
)
// User action that results in "ActivityB" activity being launched.
onView(withId(R.id.view_id))
.perform(click())
// Assert that the data we set up above is shown.
onView(withId(R.id.another_view_id)).check(matches(matches(isDisplayed())))
}
Assuming a validation like below on onActivityResult(requestCode: Int, resultCode: Int, data: Intent?)
if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) {
data?.getBooleanExtra(KEY_VALUE_TO_RETURN, false)?.let {showView ->
if (showView) {
another_view_id.visibility = View.VISIBLE
}else{
another_view_id.visibility = View.GONE
}
}
}
I follow this guide as reference : https://developer.android.com/training/testing/espresso/intents and also i had to check this links at the end of the above link https://github.com/android/testing-samples/tree/master/ui/espresso/IntentsBasicSample
and
https://github.com/android/testing-samples/tree/master/ui/espresso/IntentsAdvancedSample
If you're using ActivityScenario (or ActivityScenarioRule) as is the current recommendation in the Android Developers documentation (see the Test your app's activities page), the ActivityScenario class offers a getResult() method which you can assert on as follows:
#Test
fun result_code_is_set() {
val activityScenario = ActivityScenario.launch(BarActivity::class.java)
// TODO: execute some view actions which cause setResult() to be called
// TODO: execute a view action which causes the activity to be finished
val activityResult = activityScenario.result
assertEquals(expectedResultCode, activityResult.resultCode)
assertEquals(expectedResultData, activityResult.resultData)
}
Related
My app consists of playing simple games and earn some points and then withdraw these points to an external account. I am saving these points in the SharedPreferences to avoid multiple requests to the server (I still validate them when the user wants to withdrawn, but this is not relevant for my problem).
Now, the code goes something like this:
UserActiviy
private fun userClickedToWithdraw() {
startActivityForResult(Intent(this, WithdrawActivity::class.java), RequestCode.RC_WITHDRAW)
}
private fun transactionSuccessfully() {
//set the user points to zero in the SharedPreferences
//and updates the UI accordingly
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == RequestCode.RC_WITHDRAW && resultCode == RESULT_OK) transactionSuccessfully()
}
WithdrawActivity
//handles the transaction and then call finishTransaction
private fun finishTransaction() {
setResult(RESULT_OK)
onBackPressed()
}
The user transfers his points, and then the finishTransaction function is called setting the result as OK and finishing the activity. The parent activity handles the onActivityResult and if the result was OK, it updates the SharedPreferences and UI.
This works perfectly on all the devices that I tested, but for some reason, some of my users are having a different outcome. When they transfer successfully, the SharedPreferences and UI are not being updated correctly.
I cannot replicate this behavior so I can fix it. So I was hoping if someone that had some similar problems could lead me here. I am thinking that maybe the resultCode is not RESULT_OK and the transactionSuccessfully is not being called because of that, but as I said, I am not being able to replicate this.
Does anyone has had a similar problem that could help me?
This is just speculation but if possible call
super.onActivityResult(requestCode, resultCode, data)
last. Ideally you want to run it first but I've had issues where it led to unexpected behavior concerning my code inside of my #override.
I'm try to register an ActivityResultContract for android.provider.Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE using kotlin and AndroidX:
class AutofillContract() : ActivityResultContract<Any?,ActivityResult>() {
override fun createIntent(context: Context, input: Any?): Intent
= Intent(android.provider.Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE)
override fun parseResult(resultCode: Int, intent: Intent?): ActivityResult
= ActivityResult(resultCode, intent)
}
val afrl = registerForActivityResult(AutofillContract()) {
if (it.resultCode == RESULT_OK) ...
else ...
}
However, when I try to use it:
afrl.launch(AutofillContract().createIntent(this, null))
I get IllegalArgumentException: Can only use lower 16 bits for requestCode, which I presume was triggered by an internal startActivityForResult() call.
I haven't used a custom ActivityResultContract before, and although it seems simple it also seems a bit sketchy to me -- I'm not sure if regarding the input as irrelevant (Any?) is the way to go, but it does seem irrelevant in this case (the first version used Intent as the input type but there doesn't seem to be a point, and the problem, "Can only use lower 16 bits..." was the same).
I'm using androidx.activity:activity-ktx:1.2.0-alpha08.
As per this issue, you get that error when you are using an older version of Fragments.
You must also upgrade your version of Fragments to androidx.fragment:fragment-ktx:1.3.0-alpha08.
I tried to implement a platform-independent wrapper for sharing files that takes a filename and an Action as a callback. Despite it does work, I am not content with my Android solution at all.
What I am currently doing is: I am registering my callback with my MainActivity, which returns an ID which I can use to identify the callback
int callbackId = MainActivity.Instance.RegisterCallback(completionHandler);
Then I start ActionShare intent with
MainActivity.Instance.StartActivityForResult(Intent.CreateChooser(intent, "Select App"), 123 | (callbackId << 8));#
The 123 is my identifier for the action.
In my MainActivity in OnActivityResult I am getting and calling my callback with
var callbackId = (requestCode & 0b1111111100000000) >> 8;
if (this.RemoveCallbackById(callbackId, out var callback))
{
callback();
}
Is there a better way implementing callbacks for intents in Xamarin.Android? I believe there must be, but being constrained to passing only a single 16 bit integer to StartActivityForResult that will be passed to OnActivityResult the possibilities seem quite limited to me.
Here is the stub of the code.
Click data item on ListView . Works as designed and opens Chrome Custom Tab :
onData(anything()).inAdapterView(withId(R.id.listView))
.atPosition(0).perform(click());
Pause(5000);
Espresso.pressBack();
Cannot seem to evaluate anything in the tab or even hit device back button.
Getting this error
Error : android.support.test.espresso.NoActivityResumedException: No
activities in stage RESUMED.
Did you forget to launch the activity. (test.getActivity() or similar)?
You can use UIAutomator (https://developer.android.com/training/testing/ui-automator.html). You can actually use both Espresso and UIAutomator at the same time. See the accepted answer on the following post for more information:
How to access elements on external website using Espresso
You can prevent opening Custom Tabs and then just assert whether the intent you are launching is correct:
fun stubWebView(uri: String) {
Intents.intending(allOf(IntentMatchers.hasAction(Intent.ACTION_VIEW), IntentMatchers.hasData(uri)))
.respondWith(Instrumentation.ActivityResult(Activity.RESULT_OK, null))
}
fun isNavigatedToWebView(uri: String) {
Intents.intended(allOf(IntentMatchers.hasAction(Intent.ACTION_VIEW), IntentMatchers.hasData(uri)))
}
This way you can avoid Espresso.pressBack() in your test.
Note that since these are using Espresso Intents, you need to either use IntentsTestRule or wrap these with Intents.init and release like this
fun intents(func: () -> Unit) {
Intents.init()
try {
func()
} finally {
Intents.release()
}
}
intents {
stubWebView(uri = "https://www.example.com")
doSomethingSuchAsClickingAButton()
isNavigatedToWebView(uri = "https://www.example.com")
}
A suggestion for improved readability following Mustafa answer:
intents{
Intents.intended(
CoreMatchers.allOf(
IntentMatchers.hasAction(Intent.ACTION_VIEW),
IntentMatchers.hasPackage("com.android.chrome")
)
)
}
I want to use VoiceRecognition in my application, but this application needs to install voice search.
I don't want the user to have to install another other application then return to my application to run it. I want voice search to be installed from my application, or alternatively I'd like to find a tutorial to on how to add Voice Search capability to my application.
What can I do?
Use the RecognizerIntent to fire the speech recognizer installed on your device
This can be done in a few simple steps:
Create some sort of button in your activity, and place the following code in its OnClickListener:
// Define MY_REQUEST_CODE as an int constant in your activity...I use ints in the 10000s
startVoiceRecognitionActivity(MY_REQUEST_CODE, "Say something.");
Override the onActivityResult() method in your activity. In the implementation, place a switch block or if statement to run some logic when the requestCode argument matches your MY_REQUEST_CODE constant. Logic similar to the following will get you the list of results the speech recognition activity thought it heard:
ArrayList keywordMatches = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
You may get 0 or many matches from the recognizer. Be sure to handle all cases.
In some cases, the speech recognizer may not even be on the device. Try to handle that where you call startVoiceRecognitionActivity().
I found this tutorial :
http://www.jameselsey.co.uk/blogs/techblog/android-how-to-implement-voice-recognition-a-nice-easy-tutorial/
hope this helps.
Android Open Source VoiceRecognition Example
Here is a Simple way to Handle Voice Search
Step 1 Call this method on button click
public void startVoiceRecognition() {
Intent intent = new Intent("android.speech.action.RECOGNIZE_SPEECH");
intent.putExtra("android.speech.extra.LANGUAGE_MODEL", "free_form");
intent.putExtra("android.speech.extra.PROMPT", "Speak Now");
this.mContainerActivity.startActivityForResult(intent, 3012);
}
Step 2 Override onActivityResult method
# Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 3012 && resultCode == RESULT_OK) {
ArrayList < String > matches = data.getStringArrayListExtra("android.speech.extra.RESULTS");
String result= matches.get(0);
//Consume result
edittext.setText(result);
}
}
Thats all, DONE