11

Using the old JUnit3-style tests in Android, I could do the following to destory and restart an activity:

Instrumentation inst = getInstrumentation();
Activity activity = inst.getActivity();
// do something
activity.finish();
Assert.assertTrue(this.activity.isFinishing());
activity = inst.getActivity();
// assert that activity's state is restored

How can I accomplish the same thing using the new Testing Support Library? I'm fine with using either Espresso and/or UI Automator or any other mechanism provided by the new library.

Update:

I tried the following:

Activity activity = activityTestRule.getActivity();
// do something
activity.finish();
Assert.assertTrue(this.activity.isFinishing());
activity = activityTestRule.getActivity();
// assert that activity's state is restored

However, it appears that ActivityTestRule.getActivity() does not restart the activity.

piotrek1543
  • 19,130
  • 7
  • 81
  • 94
Code-Apprentice
  • 81,660
  • 23
  • 145
  • 268
  • I would assume that the `finish()` part should be no different than before. I don't know if `getActivity()`, called on your `ActivityTestRule`, will re-create a destroyed activity or not. – CommonsWare Feb 12 '16 at 19:52
  • @CommonsWare I tried that and it doesn't seem to restart the activity. – Code-Apprentice Feb 12 '16 at 20:17
  • Now that I think about it, I'm not quite sure what state you're expecting to get restored after a `finish()`. You could try calling `launchActivity()` after the `finish()` and see what happens, though that will create a fresh instance. Or, you could probably add it yourself. [Fork `ActivityTestRule`](https://android.googlesource.com/platform/frameworks/testing/+/android-support-test/rules/src/main/java/android/support/test/rule/ActivityTestRule.java) and hack away! – CommonsWare Feb 12 '16 at 20:39
  • @CommonsWare Perhaps this is an XY problem. The original motivation is to test the following sequence in my app: 1. Start the activity. 2. Enter some data. 3. Destroy the activity, triggering `onSaveInstanceState()`. 4. Restore the activity with the previous state. 5. Assert that the previously entered data is still in the correct views. --- So is there a better way to test this? – Code-Apprentice Feb 12 '16 at 20:45
  • 2
    "Perhaps this is an XY problem" -- um, well, I'm male, so that's my chromosome pair, if that's what you mean. :-) "Destroy the activity, triggering onSaveInstanceState()" -- `onSaveInstanceState()` is not called when the activity is destroyed. It is called because the activity undergoes a configuration change. `finish()` should not be triggering `onSaveInstanceState()`. "So is there a better way to test this?" -- you could try `UIAutomation.setRotation()` and see if that triggers `onSaveInstanceState()`. – CommonsWare Feb 12 '16 at 20:57
  • @CommonsWare [What is an XY problem?](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) – Code-Apprentice Feb 12 '16 at 21:15
  • @CommonsWare From what I understand, an orientation change will cause the activity to be destroyed. Is that incorrect? – Code-Apprentice Feb 12 '16 at 21:16
  • "What is an XY problem?" -- yes, but my interpretation is funny! "an orientation change will cause the activity to be destroyed" -- correct. However, the inverse is not true: destroying an activity does not cause a configuration change. `onSaveInstanceState()` is tied to configuration changes (plus some task-related scenarios, but there only for non-destroyed activities). `onSaveInstanceState()` is not tied to being destroyed for other reasons. – CommonsWare Feb 12 '16 at 21:36
  • @Code-Apprentice but can u say pleas how u resolve this problem in espresso tests? – Morozov Feb 21 '17 at 13:59
  • @Morozov I have not had the time to verify any of the answers since I asked this question. – Code-Apprentice Feb 21 '17 at 15:28

4 Answers4

8

As @CommonsWare mentioned, a custom rule is pretty useful. Here's my simple solution (many improvements are possible, but this is just a quick framework that can be built on):

public class ControlledActivityTestRule<T extends Activity> extends ActivityTestRule<T> {
    public ControlledActivityTestRule(Class<T> activityClass) {
        super(activityClass, false);
    }

    public ControlledActivityTestRule(Class<T> activityClass, boolean initialTouchMode) {
        super(activityClass, initialTouchMode, true);
    }

    public ControlledActivityTestRule(Class<T> activityClass, boolean initialTouchMode, boolean launchActivity) {
        super(activityClass, initialTouchMode, launchActivity);
    }

    public void finish() {
        finishActivity();
    }

    public void relaunchActivity() {
        finishActivity();
        launchActivity();
    }

    public void launchActivity() {
        launchActivity(getActivityIntent());
    }
}

Note, if you do it like this, this class needs to be in the package android.support.test.rule to access the package private method ActivityTestRule#finishActivity. Then in your test case, implement this rule:

@Rule
public ControlledActivityTestRule<TestFountainPreferenceActivity> actRule = new ControlledActivityTestRule<>(TestFountainPreferenceActivity.class);

Then in your individual test case, call actRule.finish() and actRule.launchActivity() to kill and restart it. Or you can call actRule.relaunchActivity() if you don't need to do anything in between killing it and starting it up again. Note, you can pass the third parameter false to delay starting the activity if you have initial startup, and then call actRule.launchActivity() to get it going, but you will loose access to some of the built in handles such as #afterActivityLaunched and #afterActivityFinished().

JCricket
  • 1,318
  • 2
  • 17
  • 34
  • That's correct, #finishActivity is package protected, so I expose it with the provided #finish method. – JCricket Nov 16 '16 at 20:11
  • 1
    is there any way to extend it so that recreated activity receives Bundle from onSaveInstanceState? – MateuszL Feb 28 '17 at 21:20
2

As JCricket and djunod says the way to do it is creating a custom rule. The problem that I faced with their solutions is that the method finishActivity is package protected and you can use it in your custom test rule.

This is my solution:

public class RelaunchActivityRule<T extends Activity> extends ActivityTestRule<T> {

  public RelaunchActivityRule(Class<T> activityClass) {
    super(activityClass,false);
  }

  public RelaunchActivityRule(Class<T> activityClass, boolean initialTouchMode) {
    super(activityClass, initialTouchMode,true);
  }

  public RelaunchActivityRule(Class<T> activityClass, boolean initialTouchMode,
      boolean launchActivity) {
    super(activityClass, initialTouchMode, launchActivity);
  }

  @Override protected void afterActivityFinished() {
    super.afterActivityFinished();
    launchActivity(getActivityIntent());
  }
}
emaleavil
  • 591
  • 7
  • 14
0

JCricket's answer worked for me once I added a sleep in there...

package android.support.test.rule;

public class ControlledActivityTestRule<T extends Activity> extends ActivityTestRule<T> {
    public ControlledActivityTestRule(Class<T> activityClass) {
        super(activityClass, false);
    }

    public ControlledActivityTestRule(Class<T> activityClass, boolean initialTouchMode) {
        super(activityClass, initialTouchMode, true);
    }

    public ControlledActivityTestRule(Class<T> activityClass, boolean initialTouchMode, boolean launchActivity) {
        super(activityClass, initialTouchMode, launchActivity);
    }

    public void finish() {
        finishActivity();
    }

    public void relaunchActivity(int seconds) {
        finishActivity();
        sleep(seconds);
        launchActivity();
        sleep(seconds);
    }

    public void launchActivity() {
        launchActivity(getActivityIntent());
    }

    public void sleep(int seconds) {
        if (seconds > 0) {
            try {
                Thread.sleep(seconds * 1000);
            } catch (Exception ex) {
            }
        }
    }
}

I was then able to do a test with iterations:

@Rule
public ControlledActivityTestRule<MainActivity> mActivityRule = new ControlledActivityTestRule<>(MainActivity.class);

@Test
public void testOAA310() {
    int count = 1000;
    for (int i = 0; i < count; i++) {
        testCaseOAA310();
        mActivityRule.relaunchActivity(5);
    }
}

void testCaseOAA310() { /* ... blah blah blah... */ }
djunod
  • 4,876
  • 2
  • 34
  • 28
  • Why does `testOAA310()` call itself? This appears to be an infinite recursion. – Code-Apprentice Nov 14 '16 at 11:43
  • testOAA310 calls testCaseOAA310... not itself. – djunod Nov 14 '16 at 11:47
  • What is the difference in those method names? – Code-Apprentice Nov 14 '16 at 11:50
  • OIC...test vs testCase... that seems like a poor distinction because it was not immediately obvious to me – Code-Apprentice Nov 14 '16 at 11:52
  • 1
    Just out of curiosity, what was failing that you need to put a sleep in? It's not horrible since it's in testing, but i hate putting in sleeps because it breaks the trust of a proper testing scenario. I had some initial problems with synchronicity, but usually traced it back to some other coding error that left something active that should have been killed. – JCricket Nov 16 '16 at 20:17
  • I believe getInstrumentation().waitForIdleSync(); would be prefferable alternative to sleep – MateuszL Feb 28 '17 at 20:13
0

Try creating a custom rule. Call launchActivity(getActivityIntent()) in afterActivityFinished method, it will restart the activity after finish.

public class RestartActivityRule<T extends Activity> extends ActivityTestRule<T> {

    public RestartActivityRule(Class<T> activityClass) {
        super(activityClass,false);
    }

    public RestartActivityRule(Class<T> activityClass, boolean initialTouchMode) {
        super(activityClass, initialTouchMode,true);
    }

    public RestartActivityRule(Class<T> activityClass, boolean initialTouchMode,
      boolean launchActivity) {
        super(activityClass, initialTouchMode, launchActivity);
    }

    @Override protected void afterActivityFinished() {
        super.afterActivityFinished();
        launchActivity(getActivityIntent());
    }
}
PEHLAJ
  • 9,980
  • 9
  • 41
  • 53