Search code examples
javaandroidtestingandroid-actionbarandroid-espresso

Espresso click menu item


I have a menu in the actionbar which I create through:

@Override
public boolean onCreateOptionsMenu(Menu menu) {

    menu.add(Menu.NONE, 98,Menu.NONE,R.string.filter).setIcon(R.drawable.ic_filter_list_white_48dp).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
    menu.add(Menu.NONE, 99,Menu.NONE,R.string.add).setIcon(R.drawable.ic_add_white_48dp).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);


    getMenuInflater().inflate(R.menu.menu_main, menu);

    return true;
}

and menu_main.xml looks like:

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context=".MainActivity">
    <item
        android:id="@+id/action_settings"
        android:title="@string/action_settings"
        android:orderInCategory="100"
        app:showAsAction="never"
        android:icon="@drawable/ic_settings_white_48dp"/>
</menu>

When testing in Espresso I would like to click on the "add" icon (menuId 99). I tried

@Test
public void testAdd() {
openActionBarOverflowOrOptionsMenu(InstrumentationRegistry.getTargetContext());
    onView(withText(R.string.add)).perform(click());
}

but this fails with a NoMatchingViewException. ( The settings item, which is defined in the xml directly I can click with the same code. )

That's for targetSdkVersion 23 and AppCompatActivity. The relevant lines for the tool bar are:

Toolbar toolbar = (Toolbar) findViewById(R.id.tool_bar);
setSupportActionBar(toolbar);
if( getSupportActionBar() != null ) {
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}

and tool_bar.xml looks like:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar     xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:theme="@style/ThemeOverlay.AppCompat.Dark"
    android:background="@color/ColorPrimary"
    android:elevation="4dp"
    tools:ignore="UnusedAttribute">
</android.support.v7.widget.Toolbar>

Solution

  • Update: I didn't see the final of the line, ignore my previous responses, I tried to fix it fast and I did wrong. I really didn't know the answer.

    ...setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
    

    Your question is explained here by the Espresso team:

    Matching visible icons:

      // Click on the icon - we can find it by the r.Id.
      onView(withId(R.id.action_save))
        .perform(click());
    

    Clicking on items in the overflow menu is a bit trickier for the normal action bar as some devices have a hardware overflow menu button (they will open the overflowing items in an options menu) and some devices have a software overflow menu button (they will open a normal overflow menu). Luckily, Espresso handles that for us.

      // Open the overflow menu OR open the options menu,
      // depending on if the device has a hardware or software overflow menu button.
      openActionBarOverflowOrOptionsMenu(getInstrumentation().getTargetContext());
    
      // Click the item.
      onView(withText("World"))
        .perform(click());
    

    So I understand that both alternatives are correct but depend on your specific case. If you test a button you normally know if it's visible or not at that moment.

    In your case the button is visible because there is room, and it's correct to use withId like here:

    import static android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu;
    
    @Test
    public void testClickInsertItem() {
        openActionBarOverflowOrOptionsMenu(InstrumentationRegistry.getTargetContext());
        onView(withId(R.id.action_insert)).perform(click());
    }
    

    But this it's correct too for overflow items:

    Yes, this is how it works in Espresso. The problem here is, that in Android, the View representing the menu item doesn't have the ID of the menu item. So onView(withId(X)) just fails to find a View. I don't have a better recommendation than just using withText(). If you have multiple views with the same text, using hierarchy for distinction works.

    Now my question is what to do when you test on different devices and you don't know when will be room for an item. I don't know the response, perhaps combine both solutions.