Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get current Activity on Android Instrumentation Test

My MainActivity on my Android application checks if the user is logged in (this is stored in SharedPreferences) and if it's not takes the user to the LoginActivity. I am trying to test this using the following code

public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {

private static final int TIME_OUT = 5000; /* miliseconds */

private MainActivity mMainActivity;
private Instrumentation mInstrumentation;
private SharedPreferences mLoginPrefs;

public MainActivityTest() {
    super(MainActivity.class);
}

protected void setUp() throws Exception {
    super.setUp();

    setActivityInitialTouchMode(false);

    mMainActivity = getActivity();
    mInstrumentation = getInstrumentation();
    mLoginPrefs = mInstrumentation.getTargetContext().getSharedPreferences(LoginActivity.PREFS_NAME, Context.MODE_PRIVATE);

    SharedPreferences.Editor editor = mLoginPrefs.edit();
            // User is not logged in, so it should be redirect to LoginActivity
    editor.putBoolean("logged_in", false);
    editor.commit();
}

//...

public void testC_OpenLoginActivityIfUserIsNotLoggedIn() {
    ActivityMonitor monitor = mInstrumentation.addMonitor(LoginActivity.class.getName(), null, false);
    Activity nextActivity = mInstrumentation.waitForMonitorWithTimeout(monitor, TIME_OUT);

    assertNotNull(nextActivity);
    nextActivity.finish();

    SharedPreferences.Editor editor = mLoginPrefs.edit();
            // Login the user so we can continue the tests
    editor.putBoolean("logged_in", true);
    editor.commit();
}

But this doesn't work, the LoginActivity opens but waitForMonitorWithTimeout never returns so I got stuck on LoginActivity (I need to get back to MainActivity to do the other tests).

A code similar to this SO Question works for Button clicks, but this Activity is not loaded by any click so I am thinking maybe there is no time to the monitor to work.

I just need a way to get the actual Activity so I can make an assert and make it finish to continue my tests.

Just one other thing: I would prefer a method without using Robotium if it's possible.

like image 535
m45t3r Avatar asked Apr 22 '14 02:04

m45t3r


2 Answers

In order to solve your problem, first take a look at the two most important methods for your test:

Instrumentation#addMonitor(java.lang.String, android.app.Instrumentation.ActivityResult, boolean)
Instrumentation.ActivityMonitor#waitForActivity()

According to Android API reference:

addMonitor

Add a new Instrumentation.ActivityMonitor that will be checked whenever an activity is started. The monitor is added after any existing ones; the monitor will be hit only if none of the existing monitors can themselves handle the Intent.

waitForActivity

Block until an Activity is created that matches this monitor, returning the resulting activity.


Now let's make it a bit more clear.

addMonitor should be called always before the expected activity being started, never too late.

waitForActivity should be called only after the expected activity being started, never too early, since it will block.


Back to your code:

You're calling both of them together, without any magic happening in between. So it's either too late for addMonitor, or too early for waitForActivity.

ActivityMonitor monitor = mInstrumentation.addMonitor(LoginActivity.class.getName(), null, false);
Activity nextActivity = mInstrumentation.waitForMonitorWithTimeout(monitor, TIME_OUT);

If it's too early for calling waitForActivity, it will block and fail until the timeout (because the expected activity is not hit yet), and you would never see the expected activity being started.

If it's too late for calling addMonitor, the monitoring starts after the expected activity is launched, and the expected activity is not launched again since then, so waitForActivity will block because of no hit of the monitor.

So the difference between the two cases is whether the expected activity is started or not. And for your case, I think it's too late for calling addMonitor.

The solution is very easy: just move addMonitor to a early enough position before your LoginActivity starts, maybe move it to the setUp method, like this:

mInstrumentation = getInstrumentation();
ActivityMonitor monitor = mInstrumentation.addMonitor(LoginActivity.class.getName(), null, false);

BTW, for your case, with timeout or without timeout doesn't matter.

Don't forget to remove the monitor after it's not needed anymore e.g:

@Override
protected void tearDown() throws Exception {
    mInstrumentation.removeMonitor(monitor);
    super.tearDown();
}
like image 102
Jing Li Avatar answered Oct 26 '22 06:10

Jing Li


public static Activity getCurrentActivity() {
    final Activity[] currentActivity = {null};
    getInstrumentation().runOnMainSync(new Runnable() {
        public void run() {
            Collection resumedActivities = ActivityLifecycleMonitorRegistry.getInstance()
                    .getActivitiesInStage(RESUMED);
            if (resumedActivities.iterator().hasNext()) {
                currentActivity[0] = (Activity) resumedActivities.iterator().next();
            }
        }
    });
    return currentActivity[0];
}
like image 26
tim4dev Avatar answered Oct 26 '22 06:10

tim4dev