Search code examples
androidexpresso

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 (i.e 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).enter image description here

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?


Solution

  • 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.