简体   繁体   中英

Android Expresso verifying Context Menu and actionBar items

I have a list where every row contains a name and a button that invokes a Context Menu of options. I would like to write a test that verifies the following things

  1. the context menu contains the correct NUMBER of items
  2. the context menu contains the correct VALUES
  3. the context menu does not contain any unwarranted options (the checks 1and 2 above will test this case)

I would also like to test the contents of the actionBar and actionBar overflow menu when the item is long selected.

For both tests, I can write a check that ensures there is a view element with the correct "label" displayed (ie finding the view using onView(withText(this.elementText)). However I have 2 actions which have the same label but different IDs and I need to ensure the correct action is in the context menu/long click menu.

I can not use the ID I specified in the XML for my context menu's menu because Android's Context Menu views do not have those IDs, instead they contain an internal Android ID (see the screenshot below). 在此处输入图片说明

When I wrote tests using Robotium, i had to get all current views of a certain type and the parse through them checking if they are the actionBar items, see sample code below.

public static List<MenuItemImpl> getLongClickMenuItems(String itemName) {
    List<MenuItemImpl> menuItems = new ArrayList<>();

    // long select the item
    solo.clickLongOnText(itemName);

    // get the children of the of the long click action bar
    ArrayList<ActionMenuView> outViews = solo.getCurrentViews(ActionMenuView.class, solo.getView(R.id.action_mode_bar));

    if (!outViews.isEmpty()) {
        // get the first child which contains the action bar actions
        ActionMenuView actionMenuView = outViews.get(0);
        // loop over the children of the ActionMenuView which is the individual ActionMenuItemViews
        // only a few fit will fit on the actionBar, others will be in the overflow menu
        int count = actionMenuView.getChildCount();
        for (int i = 0; i < count; i++) {
            View child = actionMenuView.getChildAt(i);

            if (child instanceof ActionMenuItemView) {
                menuItems.add(((ActionMenuItemView) child).getItemData());
            } else {
                // this is the more button, click on it and wait for the popup window
                // which will contain a list of ListMenuItemView
                // As we are using the AppCompat the actionBar's menu items are the
                // the AppCompat's ListMenuItemView (android.support.v7.view.menu.ListMenuItemView)
                // In the context menu, the menu items are Android's native ListMenuItemView
                // (com.android.internal.view.menu.ListMenuItemView)
                solo.clickOnView(child);
                solo.waitForView(ListMenuItemView.class);
                ArrayList<ListMenuItemView> popupItems = solo.getCurrentViews(ListMenuItemView.class);
                for (ListMenuItemView lvItem : popupItems) {
                    menuItems.add(lvItem.getItemData());
                }

                // close the more button actions menu
                solo.goBack();
            }
        }
    }

    // get out of long click mode
    solo.goBack();

    return menuItems;
}

Does anyone know how I can get the list of Context Row menu items using Expresso.

Does anyone know how I can get the actionBar items (including all items in the overflow menu) using Expresso?

If I understand your question correctly, you should be able to use on the onData() method to interact with the context menus as they are just AdapterViews (notice from your screenshot the popup is a ListPopupWindow$DropDownListView ).

So you should be able to do something like this:

onView(withText("Item Label")).perform(longClick());
final int expectedItemCount = 10;

// Now the floating popup should be showing,
// first assert it has the expected item count

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

// Now assert each entry (which should just be a string) is shown correctly
for (int i = 0; i < expectedItemCount; i++) {
    String expectedItemText = getExpectedItemTextForIndex(i);
    onData(instanceOf(String.class)).atPosition(i)
        .check(matches(withText(expectedItemText)));
}

Where the withItemCount matcher above is a simple matcher that asserts against the given adapter view's adapter count:

public static Matcher<View> withNumberOfItems(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;
        }
    };
}

I think the same concept should work for an action bar overflow menu.

I'm not clear what you mean by:

For both tests, I can write a check that ensures there is a view element with the correct "label" displayed (ie finding the view using onView(withText(this.elementText)). However I have 2 actions which have the same label but different IDs and I need to ensure the correct action is in the context menu/long click menu.

But the atPosition should get you exactly the view you're interested in the list, and you can add onChildView if you need to target as sub-view within a given item in the list.

Hope that helps!

Thank you so much to dominicoder for pretty much giving me the answer to this question. working on their reply i have managed to get it working.

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

I then use the exact same matcher "dominicoder" provided to verify the context menu item count.

Using "dominicoder's" sample matchers I was able to code one myself 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.

Huge thank you to "dominicoder". Its a shame you can not mark both answers as correct as you did actually give me virtually the correct answer.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM