android – Activity lifecycle unit testing-ThrowExceptions

Exception or error:

In an activity unit test how can I emulate the activity lifecycle events.

I can call the instrumentation’s callActivityOn… methods on the existing activity, but how do I trigger activity recreation so that the activity’s OnCreate gets the bundle with saved state

How to solve:

I have found that this code cause new Activity to be created:

myActivity.finish();
setActivity(null);
myActivity = getActivity();

But this does not cause onSaveInstanceState to be called. So for example to test if activity is propely created after view orientation change such test should do it:

private mInstrumentation = getInstrumentation();
...
final Bundle outState = new Bundle();
mInstrumentation.callActivityOnSaveInstanceState(mActivity, outState);
mActivity.finish();
setActivity(null);
mActivity = getActivity();
runTestOnUiThread(new Thread() {
    @Override
    public void run() {
        mInstrumentation.callActivityOnRestoreInstanceState(mActivity, outState);
    }
});

###

Do not follow the state management test example : {dead link}

myActivity.finish();
myActivity = getActivity();

ActivityInstrumentationTestCase2.getActivity() starts the Activity the first time you call it, but then it simply returns that same Activity in each subsequent call in the test case. Thus, you are still looking at the Activity that you finished.

After you finish the first Activity, you need to start a new one from the test. You can use InstrumentationTestCase.launchActivity(), for example.

As another example, I’ve written a test that pushes a button in ActivityA that launches ActivityB for-result; the test then immediately kills ActivityA (via an orientation change, but finish() would work, too), and then the test gets a handle to the new ActivityA that the system creates when ActivityB is done and sends its result. The trick there was to have the test add an Instrumentation.ActivityMonitor and then have that monitor wait for the system to start the new ActivityA and give the test a handle to it.

EDIT 2/23/2012 cdhabecker, Adding reproducible code:

public class VerboseActivity extends Activity {
    public final static String TAG = "Verbose";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.i(TAG, "onCreate() " + (Activity)this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity5);
    }
    @Override
    protected void onDestroy() {
        Log.i(TAG, "onDestroy().");
        super.onDestroy();
    }
}

Test case: (the sleep() calls give the activity lots of time to respond)

public class VerboseTest extends
        ActivityInstrumentationTestCase2<VerboseActivity> {

    Activity myActivity = null;

    public VerboseTest() {
        super("com.scanillion.demo", VerboseActivity.class);
    }

    public void test_01() {
        String TAG = "test_01";
        myActivity = getActivity();
        Log.i(TAG, "A getActivity()=" + myActivity);
        myActivity.finish();
        try {
            Thread.sleep(5000L);
        } catch (InterruptedException e) {
        }
        myActivity = getActivity();
        Log.i(TAG, "B getActivity()=" + myActivity);
        try {
            Thread.sleep(5000L);
        } catch (InterruptedException e) {
        }
    }
}

Log:

02-23 21:25:37.689: I/Verbose(17747): onCreate() com.scanillion.demo.VerboseActivity@43ba3360
02-23 21:25:38.159: I/ActivityManager(67): Displayed activity com.scanillion.demo/.VerboseActivity: 526 ms (total 526 ms)
02-23 21:25:38.180: I/test_01(17747): A getActivity()=com.scanillion.demo.VerboseActivity@43ba3360
02-23 21:25:38.540: I/Verbose(17747): onDestroy().
02-23 21:25:43.236: I/test_01(17747): B getActivity()=com.scanillion.demo.VerboseActivity@43ba3360
02-23 21:25:48.439: I/TestRunner(17747): finished: test_01(com.scanillion.demo.test.VerboseTest)
02-23 21:25:48.439: I/TestRunner(17747): passed: test_01(com.scanillion.demo.test.VerboseTest)

Note that finish() caused onDestroy(), but the subsequent getActivity() was a no-op. Not only does getActivity() not instantiate a new Activity, it doesn’t even recreate the original one.

###

I confirm that cdhabecker is right, getActivity() returns the activity that was created at the beginning, even if you “finish” it. But I think I’ve found a solution to test the recreation of activity. You can try to request orientation change. This will recreate your activity, and then you retrieve newly created. Code snippet below: (I used robotium):

protected void setUp() throws Exception {
  super.setUp();
  mActivity = getActivity();
  mSolo = new Solo(getInstrumentation(), getActivity());
  Log.v(TAG, "setUp; activity=" + mActivity);
}

public void testOrienationChange(){     
  mSolo.setActivityOrientation(Solo.LANDSCAPE);
  getInstrumentation().waitForIdleSync();
  MyActivity newActivity = getActivity(); //should be new, but it's not
  Activity newActivity2 = mSolo.getCurrentActivity(); //this will return newly created
  Log.v(TAG, "testOrienationChange; activity=" + newActivity);
  Log.v(TAG, "testOrienationChange; activity2=" + newActivity2);
}   

Of course it will not work if you prevent your activity from being destroyed after orientation change. Here you can find my full answer with log messages included. Hope that helps. Regards!

###

If you have an Android 4.x device, you can go into Settings > Developer options and CHECK ‘Don’t keep activities’. Now whenever your Activity loses focus (ex: HOME button), it will be killed and onSaveInstanceState(…) will be called.

When you resume your app, your Activity should have bundle data in the onCreate(…) method if you saved it in onSaveInstanceState(…).

###

There is a very good example in the official dev guide talking about state management tests here. Basically you just need call Activity.finish() to emulate activity has been killed, check out pseudo code below:

public void testIfStateIsSaved() {
  // Open myActivity first time.
  MyActivity myActivity = getActivity();
  final EditText editText = (EditText) myActivity.findViewById(com.company.R.id.edit_text);
  // emulate some user action
  myActivity.runOnUiThread(new Runnable() {
    public void run() {
      editText.setText("save me");
    }
  });

  // Suppose you have implemented saved state properly.

  // kill activity and restart it again.
  myActivity.finish();
  myActivity = getActivity();
  final EditText editText2 = (EditText) myActivity.findViewById(com.company.R.id.edit_text);
  assertEquals("user input must be saved", "save me", editText2.getText());
}

Hope this helps.

###

Elaborating on cdhabecker’s answer, I created the following static method which works for me:

public static Activity restartActivity(Activity activity, Instrumentation instrumentation, Intent intent){
    String className = activity.getClass().getName();
    Instrumentation.ActivityMonitor monitor = instrumentation.addMonitor(className, null, false);
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.setClassName(instrumentation.getTargetContext(), className );
    instrumentation.startActivitySync(intent);
    Activity newActivity = instrumentation.waitForMonitor(monitor);
    instrumentation.removeMonitor(monitor);
    return newActivity;
}

After using the activity, I destroy it and reset by calling

activity.finish();
setActivity(null);

in my ActivityInstrumentationTestCase2 class.

###

You can get new resumed Activity through ActivityLifeCycleMonitor

For example, this method wait and sets new created Activity as current Activity.

public void waitAndSetResumedActivity() {
    // well at least there are some activities in the pipeline - lets see if they resume.

    long[] waitTimes =
            {10, 50, 100, 500, TimeUnit.SECONDS.toMillis(2), TimeUnit.SECONDS.toMillis(30)};

    final ActivityLifecycleMonitor activityLifecycleMonitor = ActivityLifecycleMonitorRegistry.getInstance();
    final AtomicBoolean activityResumed = new AtomicBoolean(false);
    for (int waitIdx = 0; waitIdx < waitTimes.length; waitIdx++) {
        if (activityResumed.get()) return;
        try {
            Thread.sleep(waitTimes[waitIdx]);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        getInstrumentation().runOnMainSync(new Runnable() {
            @Override
            public void run() {
                Collection<Activity> resumedActivities = activityLifecycleMonitor.getActivitiesInStage(Stage.RESUMED);
                if (!resumedActivities.isEmpty()) {
                    activity = (MainActivity) resumedActivities.iterator().next();
                    setActivity(activity);
                    activityResumed.set(true);
                }
            }
        });

    }
    throw new NoActivityResumedException("No activities in stage RESUMED. Did you forget to "
            + "launch the activity. (test.getActivity() or similar)?");

}

So after call to this method, any call to getActivity() will return the new Activity.

You can test Activity recreation on rotation like this :

Activity activity = getActivity(); // old activity    
//rotate it
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
//set new Activity
waitAndSetResumedActivity();
activity = getActivity();  // New Activity

Leave a Reply

Your email address will not be published. Required fields are marked *