Tuesday, October 6, 2015

Espresso: Save and restore state

Do you save and restore state of your activities, fragments and custom views? Do you test them?

One way to test saving and restoring state is to rotate the screen in your Espresso test.

private void rotateScreen() {
  Context context = InstrumentationRegistry.getTargetContext();
  int orientation 
    = context.getResources().getConfiguration().orientation;

  Activity activity = activityRule.getActivity();
  activity.setRequestedOrientation(
      (orientation == Configuration.ORIENTATION_PORTRAIT) ?
          ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE : 
          ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}

With this helper function, you can write tests like this:

@Test
public void incrementTwiceAndRotateScreen() {
  onView(withId(R.id.count))
      .check(matches(withText("0")));

  onView(withId(R.id.increment_button))
      .perform(click(), click());
  onView(withId(R.id.count))
      .check(matches(withText("2")));

  rotateScreen();

  onView(withId(R.id.count))
      .check(matches(withText("2")));
}

Source code

https://github.com/chiuki/espresso-samples/ under rotate-screen

Like this article? Take a look at the outline of my Espresso book and fill in this form to push me to write it! Also check out the published courses: https://gumroad.com/chiuki

11 comments:

Inline coding questions will not be answsered. Instead, ask on StackOverflow and put the link in the comment.

  1. Yeah we do this in our Robotium UI tests (Yes I really want to switch to Espresso! :-)

    ReplyDelete
  2. did you face any issues with StrictMode? I keep getting StrictMode$InstanceCountViolation after orientation change.

    ReplyDelete
    Replies
    1. I tried adding StrictMode and it runs fine. How are you using StrictMode, and what device do you run it on? Do you get the same error when you rotate the device by hand?

      Delete
    2. I can easily reproduce the issue by adding StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().penaltyDeath().build()); to @Before in your test class. I need to double check it but it seems that ActivityThreadRule keeps a reference to the first activity. Tested with Genymotion Nexus 5 - API 21.

      Delete
    3. I've created a ticket to address this issue - https://code.google.com/p/android/issues/detail?id=189768

      Delete
  3. If you have any references that have yet to be deallocated the new instance of your activity is alive while your dead activity is trying to die, the activity will not be completely destroyed until all associated refrences to the activity are gone, Hence the instance count violation. I have seen and dealt with this before.

    -- Also with Espresso, if you are testing anything with an adapter and change the orientation you should wait before testing anything related to adapter count. if you change orientation and then talk to the adapter before its notifydatasetchanged() on an attached fragment (because fragment get detached not destroyed) is called you can get an illegal state exception. (in my case i had different getCount() values based on my orientation.)

    ReplyDelete
  4. There's also ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED, which caused some issues for me n the past. If that's the case, I switch to LANDSCAPE, since the initial state is usually PORTRAIT. Also, I had to add

    getInstrumentation().waitForIdleSync();

    since in some cases the next op/check wasn't Espresso synchronized and didn't wait for the rotation to complete.

    Thanks for your Espresso posts!

    ReplyDelete
  5. Hi, and thanks a lot for this code gem!
    I wanted to perform(scrollTo()) on views after I called the rotateScreen() method. It's important to notice that methods like this will not work any more, because the screen orientation has not changed (only the activity orientation has).

    So, beware of using methods like scrollTo() when your activity has a different orientation than the screen - they will not work.

    ReplyDelete
  6. This unfortunately will cause issues for certain activities that lock their orientations.

    What if you manually call `activty.recreate()`?

    ReplyDelete
    Replies
    1. I don't know. Can you try it and share the results? Thanks!

      Delete
    2. activity.recreate() worked great for me!

      http://stackoverflow.com/a/35139887/891242

      Delete