Skip to content

Instantly share code, notes, and snippets.

@saxophone
Last active October 13, 2020 14:43
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save saxophone/961ceceea43f8501cbaf to your computer and use it in GitHub Desktop.
Save saxophone/961ceceea43f8501cbaf to your computer and use it in GitHub Desktop.
Use Android Espresso to test that your Activity finishes with the expected result.
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import static android.support.test.InstrumentationRegistry.getInstrumentation;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* A test-specific activity that immediately starts another activity and listens for that subject's
* results
*/
public class ResultTestActivity extends AppCompatActivity {
/**
* An interface that defines all of the variables involved in testing that an activity's
* results are as expected
*/
public interface ActivityResultTest {
/**
* @return the intent with the appropriate extras that will start your subject activity
*/
Intent getSubjectIntent();
/**
* Perform the necessary UI actions necessary to trigger the target activity finishing with
* a result.
*/
void triggerActivityResult();
/**
* @return a matcher for the result test activity to match the target activity's result
*/
Matcher<ResultTestActivity> getActivityResultMatcher();
}
private final static String EXTRA_START_ACTIVITY_INTENT = "extraStartActivityIntent";
private final static int REQUEST_CODE = 9999;
private int mResultCode;
private Intent mResultData;
/**
* A simple static method to extract intent creation for this test activity
*
* @param subjectIntent The intent that will start the activity being tested.
*/
public static Intent createIntent(Intent subjectIntent) {
Intent intent = new Intent(getInstrumentation().getTargetContext(), ResultTestActivity.class);
intent.putExtra(EXTRA_START_ACTIVITY_INTENT, subjectIntent);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
return intent;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE) {
mResultCode = resultCode;
mResultData = data;
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
Intent subjectIntent = getIntent().getParcelableExtra(EXTRA_START_ACTIVITY_INTENT);]
startActivityForResult(subjectIntent, REQUEST_CODE);
}
}
/**
* Execute the steps necessary for testing whether a subject activity finishes with the
* activity results expected
*
* @param test Define the subject's intent, actions required to finish the subject activity,
* and a matcher for ResultTestActivity, likely receivedExpectedResult();
*/
public static void runActivityResultTest(ActivityResultTest test) {
// Start an instance of ActivityResultTest, using the subject Activity's intent as an extra
getInstrumentation().startActivitySync(test.getSubjectIntent());
// Perform actions that will cause your subject to finish with a result
test.triggerActivityResult();
// If you have the Developer option "Don't keep activities" enabled on your testing device,
// the instance of ResultTestActivity from startActivitySync() will be different from the
// ResultTestActivity that will actually receive your subject's results. Since it doesn't
// matter which instance of ResultTestActivity gets results, we use a testing helper method
// to get the current activity, which we assume will be a ResultTestActivity
ResultTestActivity resultTestActivity = (ResultTestActivity) TestUtils.getCurrentActivity();
// Match the current ResultTestActivity against your expectations for the results as
// defined in the input ActivityResultTest
assertThat(resultTestActivity, test.getActivityResultMatcher());
// resultTestActivity has been created in this method and if we don't finish it here, it
// will continue to exist on your device. There's no need for that, so kill it.
resultTestActivity.finish();
}
/**
* A matcher that determines if the ResultTestActivity has received the activity results expected
* from your subject activity
*
* @param resultCodeMatcher Matcher for the result code
* @param resultDataMatcher Matcher for the data in the resultant Intent
*/
public static Matcher<ResultTestActivity> receivedExpectedResult(final Matcher<Integer> resultCodeMatcher, final Matcher<Intent> resultDataMatcher) {
return new TypeSafeMatcher<ResultTestActivity>() {
@Override
protected boolean matchesSafely(ResultTestActivity item) {
return resultCodeMatcher.matches(item.mResultCode) && resultDataMatcher.matches(item.mResultData);
}
@Override
public void describeTo(Description description) {
description.appendText("with result code=").appendDescriptionOf(resultCodeMatcher);
description.appendText(" and with intent=").appendDescriptionOf(resultDataMatcher);
}
};
}
@Override
public String toString() {
return "Result Activity with result code=" + mResultCode + " and request data=" + mResultData;
}
}
import android.app.Activity;
import android.os.Looper;
import android.support.test.runner.lifecycle.ActivityLifecycleMonitor;
import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
import android.support.test.runner.lifecycle.Stage;
import java.util.Collection;
import static android.support.test.InstrumentationRegistry.getInstrumentation;
public class TestUtils {
public static Activity getCurrentActivity() {
if (Looper.myLooper() == Looper.getMainLooper()) {
return getCurrentActivityOnMainThread();
} else {
final Activity[] topActivity = new Activity[1];
getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
topActivity[0] = getCurrentActivityOnMainThread();
}
});
return topActivity[0];
}
}
private static Activity getCurrentActivityOnMainThread() {
ActivityLifecycleMonitor registry = ActivityLifecycleMonitorRegistry.getInstance();
Collection<Activity> activities = registry.getActivitiesInStage(Stage.RESUMED);
return activities.iterator().hasNext() ? activities.iterator().next() : null;
}
}
@rciovati
Copy link

Thanks for the gist, very useful!

Just one about about a typo. At line 85 I guess you meant:

getInstrumentation().startActivitySync(createIntent(subjectIntent));

instead of:

 getInstrumentation().startActivitySync(test.getSubjectIntent());

@abigailnguyen
Copy link

Thank you very much for your resolution. It solves my problem 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment