Espresso: match a view under a dialog

Augier :

My test case is fairly simple: on the main activity view, I have a drawer. One menu item in this drawer opens a dialog. I want to assert that, this menu item is clicked, the drawer is closed before the dialog is opened.

Here is what I have now:

// Opens the drawer
onView(withId(R.id.activity_main_navigation_drawer)).perform(DrawerActions.open())
// Check that the drawer is opened
onView(withId(R.id.activity_main_navigation_drawer)).check(matches(isOpen()))
// Click on the menu item that closes the drawer and display the dialog
onView(withText(R.string.title_add_subscription)).perform(click())
// Check that the drawer is now closed: this won't work
onView(withId(R.id.activity_main_navigation_drawer)).check(matches(isClosed()))

Note that I don't want to dismiss the dialog yet. Because the goal is to assert that the drawer was closed before/during the display of the dialog.

The problem is the last instruction will rise a NoMatchingViewException as the drawer doesn't seem to be in the view herarchy anymore. It looks like displaying the dialog make it the new root of the view hirarchy. Still, I know that the activity view exists somewhere. I'd like to know, in this case, how to match the vie under the dialog when it is shown.

Prethia :

This is a really good question and I am surprised it hasn't been asked/discussed anywhere else before.

Your reasoning is correct, when the dialog appears the view hierarchy changes so onView(withId(R.id.activity_main_navigation_drawer))gives NoMatchingViewException. Also assigning it to a ViewInteractionobject before opening the dialog doesn't help since Espresso will try to match the object using the new view hierarchy even if you want to use the old object(that was matched using the old view hierarchy)

The solution I am gonna suggest should work for you and as far as I know is the only way to achieve what you want. This is kind of against the spirit of UI testing since we only want to imitate user's actions and try to assert things the way user see the system but I would argue this solution is still OK since we are not interacting with the system using our system code. (we only use it to assert something)

 Activity activity = getActivityInstance();
 DrawerLayout drawerLayout=(DrawerLayout) activity.findViewById((R.id.drawerlayout));
// your test code
 assertFalse(drawerLayout.isDrawerOpen(GravityCompat.START))
// your remaining test code

Method for getActivityInstance(There are more than 999999 different ways to get the activity instance. This one is taken from get activity instance

private Activity getActivityInstance(){
    final Activity[] currentActivity = {null};

    getInstrumentation().runOnMainSync(new Runnable(){
      public void run(){
        Collection<Activity> resumedActivity = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED);
        Iterator<Activity> it = resumedActivity.iterator();
        currentActivity[0] = it.next();
      }
    });

    return currentActivity[0];
  }

As a personal preference, I don't like to use the instance of activity, unless it is to test something really important and I really can't test it any other way.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=8651&siteId=1