How to stop and restart an activity in an android instrumentation test?
Asked Answered
E

6

21

I'm trying to write an Android activity instrumentation test that stops (onPause(), then onStop()) and restarts the current activity. I tried

activity.finish();
activity = getActivity();

...but that doesn't seem to work properly.

The goal of the test is to assert that form data is stored during the onPause() method and re-read during the onStart() method. It works when doing it manually, but the test fails, from which I draw the conclusion that activity.finish() seems to be the wrong way to stop and restart an activity.


Edit: My main problem seems to have been a synchronization issue. After restarting the activity, the test runner didn't wait for all event handlers to finish. The following line halts the test execution until the activity is idle:

getInstrumentation().waitForIdleSync()

Besides that, take a look at the accepted answer for more valuable information about the lifecycle.

Eaten answered 24/4, 2012 at 10:26 Comment(3)
What exactly doesn't seem to work properly?Mailbag
What do you mean when you say, "doing it manually?"Smokeless
@JoelSkrepnek There is a checkbox that enables and disables the feature. When I uncheck the checkbox and close and reopen the app, all data is gone. When I check it and close or kill and then reopen the app, all form data is restored.Eaten
M
15

By calling (or trigger a screen orientation change):

activity.finish(); // old activity instance is destroyed and shut down.
activity = getActivity(); // new activity instance is launched and created.

Causing the activity go through the complete recreation life cycle:

onPause() -> onStop() -> onDestroy() -> onCreate()

What you need is:

onPause() -> onStop() -> onRestart()

I exposed the Instrumentation API recently and found plenty of interesting activity life cycle trigger method callActivityOnXXX(), the following single line of code should do the tricky:

MyActivity myActivity = getActivity();
// make activity falling into restart phase:
getInstrumentation().callActivityOnRestart(myActivity);

Activity life cycle diagram quoting from official dev guide: enter image description here

Mailbag answered 24/4, 2012 at 22:51 Comment(5)
Actually, I already did give the first solution a try, and it did not seem to work as you can see in my question. But maybe you're right and the problem is unrelated... Re your second suggestion, I'll try that one later on...Eaten
that actually did it, thanks very much! :) would you mind putting this in the top of your answer for others to find more easily?Eaten
Thanks for the edit. One last remark: I also ran into synchronization problems. The test didn't wait for my lifecycle eventhandler to finish. Adding getInstrumentation().waitForIdleSync() after restarting the activity helped.Eaten
Does getInstrumentation().callActivityOnRestart(myActivity) really results into onPause() -> onStop() -> onRestart()? I tried and it is not.Chemosphere
@siik, it is hard to tell without more details and investigation. My first shot is make sure callActivityOnRestart() is called off the application's main thread (or UI thread), i.e. in case if using @UiThreadTest annotation wrap the whole test method body.Mailbag
B
10

I tried calling .finish(), setActivity(null), getActivity() and it does restart the activity, but for me it was not restoring the state. I tried out all the other answers on SO, and every other method to do this I could find online, and none of them worked for me. After much experimentation I found the following works (nb: requires API level 11+):

    getInstrumentation().runOnMainSync(new Runnable() {
        @Override
        public void run() {
            activity.recreate();
        }
    });
    setActivity(null);
    activity = getActivity();

When I do this a new Activity instance is created, and a new instance of the fragment I had attached to the activity earlier in the test is also created, and both the activity and fragment restore their state in the expected manner.

I don't know how this works or why this works, I reached this solution through trial and error, and I have only tested it on a Nexus 4 running KitKat. I can't guarantee it correctly simulates an activity recreation, but it worked for my purposes.

Edit: At a later date I figured out how this works. getActivity() works through registering hooks that receive new Activities being created, which catch the new Activity created by activity.recreate(). setActivity(null) was required to clear the internal cache backing getActivity, otherwise it will return the old one and not look for a new one.

You can see how this works from examining the source code for the various test case classes one extends from.

Bimbo answered 14/1, 2014 at 11:40 Comment(0)
S
2

A good way to test lifecycle events is through screen orientation changes. In my experience it's a convenient way to bombproof the onPause / onStart pattern.

Smokeless answered 24/4, 2012 at 12:28 Comment(4)
Interesting idea. The problem with this is that Android saves form data by default when doing a screen orientation change, so that doesn't really cover this test case...Eaten
A screen orientation change resulting complete Activity life cycle: ... -> onPause() -> onStop() -> onDestroy() -> onCreate() -> onStart() -> ..., which has the same behavior of calling activity.finish(); activity = getActivity();Mailbag
@DaniloBargen I don't believe form data is saved by default when an activity is destroyed.Smokeless
@JoelSkrepnek I tested it on my ICS device. killing or closing the app results in an empty form field. When changing the screen orientation, all form data is retained.Eaten
O
0

Maybe u could try to save the name of your activity, finish it... and use reflection to get a new instance of the .class for the new intent to create...

Operatic answered 24/4, 2012 at 12:20 Comment(0)
L
0

Change your code as follows:

mActivity.finish();
    setActivity(null);
    mActivity = this.getActivity();

A full explanation can be found in this question

Livesay answered 22/1, 2013 at 13:8 Comment(0)
P
0

ActivityScenario.recreate() seems to work fine. I don't think the other complex solutions are needed anymore.

Given this test

@Test
fun activity_is_recreated() {
  activityScenario = ActivityScenario.launch(TestingLifecycleActivity::class.java)

  activityScenario.onActivity {
      Timber.d("inside onActivity $it")        
      //do assertions   
  }

  Timber.d("pre recreate")
  activityScenario.recreate()
  Timber.d("post recreate")

  activityScenario.onActivity {
      Timber.d("inside onActivity $it")
      //do assertions
  }
}

These are the lifecycle related logs

Lifecycle status change: TestingLifecycleActivity@e34cfb7 in: PRE_ON_CREATE
Lifecycle status change: TestingLifecycleActivity@e34cfb7 in: CREATED
Lifecycle status change: TestingLifecycleActivity@e34cfb7 in: STARTED
Lifecycle status change: TestingLifecycleActivity@e34cfb7 in: RESUMED
inside onActivity TestingLifecycleActivity@e34cfb7
pre recreate
Schedule relaunch activity: TestingLifecycleActivity
Lifecycle status change: TestingLifecycleActivity@e34cfb7 in: PAUSED
Lifecycle status change: TestingLifecycleActivity@e34cfb7 in: STOPPED
Lifecycle status change: TestingLifecycleActivity@e34cfb7 in: DESTROYED

//new activity instance is launched
Lifecycle status change: TestingLifecycleActivity@ac46813 in: PRE_ON_CREATE
Lifecycle status change: TestingLifecycleActivity@ac46813 in: CREATED
Lifecycle status change: TestingLifecycleActivity@ac46813 in: STARTED
Lifecycle status change: TestingLifecycleActivity@ac46813 in: RESUMED
post recreate
inside onActivity TestingLifecycleActivity@ac46813
Finishing activity: TestingLifecycleActivity@ac46813
Parnell answered 10/5, 2022 at 7:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.