Expresso testing of Context Menu's, ActionBar/Long Click ActionBar Overflow menus

1,011 views
Skip to first unread message

sarah....@gmail.com

unread,
Jun 18, 2017, 11:17:14 AM6/18/17
to Android Testing Support Library
How can Expresso be used to verify the content of a context menu or actionBar actions (including those in the overflow menu) or long click actionBarActions (including those in the overflow menu) are the correct?

In my application I have actions available in the following place
  1. click a button on a list item to see a context menu of actions
  2. on the actionbar there are actions, some of which are in the overflow menu
  3. if i long select a list item, the contextual action bar contains actions, some of which are in the overflow menu

I need to verify in these three scenarios, that the menus contain
  1. the correct number of actions
  2. the ids of the actions in the menus are the ids of the actions which i am expecting there to be (using the IDs specified in the XML file for the menus)
I specifically do NOT want to verify the actions by the title/label/text, i need to use the xml menu file's IDs.

I can partially test my menus but not in the manner in which i want. 

For example: With Context Menus, when it is popped up, i can loop over my expected actions and verify
onView(withText(myActionLabel)).check(matches(isDisplayed()));

I can not however use the id (as below) as the popup views do NOT have the ID that is specified in the menu XML file, it has some internal Android ID
onView(withId(myActionId)).check(matches(isDisplayed()));

For the actionBar (regular or contextual), the actions which are visible on the actionBar appear to have the same ID as in the menu XML file so for those actions I can get the views by ID (see code snippet directly above).  However, the actions in the overflow menu, are exactly like those in the Context Menu and therefore I can not use the IDs as in the menu XML file.

The image below uses Android Device Monitor to view the view hierarchy and illustrate what i am referring to.



I have had a suggestion of using the fact that the menus are list of items and therefore I can treat the views like you would lists, with code such like

onView(isAssignableFrom(AdapterView.class)).check(matches(withItemCount(expectedItemCount)));

public static Matcher<View> withItemCount(final int itemsCount) {
    return new BoundedMatcher<View, AdapterView>(AdapterView.class) {
        @Override
        public void describeTo(Description description) {
            description.appendText("with number of items: " + itemsCount);
        }

        @Override
        protected boolean matchesSafely(AdapterView item) {
            return item.getAdapter().getCount() == itemsCount;
        }
    };
}

However in the context menu case (i haven't tried the actionBar/contextual action bar case), the onView gets the "PopupWindow$PopupDecorView" view which is not assignable to the AdapterView class so it fails. How can i verify the menu contents contain the correct number of items, and then that each item in the menu is for the correct action using the ID i specified in the XML file?

sarah....@gmail.com

unread,
Jun 19, 2017, 12:44:40 PM6/19/17
to Android Testing Support Library, sarah....@gmail.com
I have found the solution to this issue with a help from a suggestion on StackOverflow (the one i posted with the original post).

Instead of using "isAssignableFrom(AdapterView.class)" I use "isAssignableFrom(ListView.class)".

I then use a matcher to verify the context menu item count. And another matcher that checks the MenuItem at a certain position in the ListView and I can compare the IDs to make sure its the ID that I am expecting.

public boolean verifyRowContextMenuContents(String name, MyActionObject[] actions){
    // invoke the row context menu
    clickRowActionButton(name);

    // The Context Menu Popup contains a ListView
    int expectedItemCount = actions.length;

    // first check the Popup's listView contains the correct number of items
    onView(isAssignableFrom(ListView.class))
            .check(matches(correctNumberOfItems(expectedItemCount)));

    // now check the order and the IDs of each action in the menu is the expected action
    for (int i = 0; i < expectedItemCount; i++) {
        onView(isAssignableFrom(ListView.class))
                .check(matches(correctMenuId(i, actions[i].getId())));
    }

    // close the context menu
    pressBack();

    return true;
}

private static Matcher<View> correctNumberOfItems(final int itemsCount) {
    return new BoundedMatcher<View, ListView>(ListView.class) {
        @Override
        public void describeTo(Description description) {
            description.appendText("with number of items: " + itemsCount);
        }

        @Override
        protected boolean matchesSafely(ListView listView) {
            ListAdapter adapter = listView.getAdapter();
            return adapter.getCount() == itemsCount;
        }
    };
}

private static Matcher<View> correctMenuId(final int position, final int expectedId) {
    return new BoundedMatcher<View, ListView>(ListView.class) {
        @Override
        public void describeTo(Description description) {
            description.appendText("with position : " + position + " expecting id: " + expectedId);
        }

        @Override
        protected boolean matchesSafely(ListView listView) {
            ListAdapter adapter = listView.getAdapter();
            Object obj = adapter.getItem(position);
            if (obj instanceof MenuItem){
                MenuItem menuItem = (MenuItem)obj;
                return menuItem.getItemId() == expectedId;
            }
            return false;
        }
    };
}   


With this code I can check the Context Menu contains the correct number of menu items, and that the items in the menu are the ones i am expecting (using ID verification) and the order I am expecting.
Reply all
Reply to author
Forward
0 new messages