androidandroid-testingandroid-navigationandroid-instrumentation

Testing Navigation from ActionBar menu


I am trying to write a test for a menu item. The idea is to click on the menu item and verify that the NavController is set to the correct destination:

public class NavigationTest {
    @Test
    public void clickOnSearchMenuNavigatesToFilter() {
        TestNavHostController navController = new TestNavHostController(ApplicationProvider.getApplicationContext());
        ActivityScenario<MainActivity> listScenario = ActivityScenario.launch(MainActivity.class);
        listScenario.onActivity(activity -> {
            navController.setGraph(R.navigation.nav_graph);
            Navigation.setViewNavController(activity.requireViewById(R.id.nav_host_fragment), navController);
        });
        Espresso.onView(ViewMatchers.withId(R.id.filter_menu)).perform(ViewActions.click());
        assertThat(Objects.requireNonNull(navController.getCurrentDestination()).getId()).isEqualTo(R.id.filter_cards);
    }
}

This test fails with

expected: 2131296468

but was : 2131296378

2131296378 is the id for the initial fragment in the NavController. What is wrong with my test? My initial thinking is that I am not injecting the TestNavHostController correctly, but debugging verified that the activity sees it as I expect:

public class MainActivity extends AppCompatActivity {
    // ...

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
        return NavigationUI.onNavDestinationSelected(item, navController)
            || super.onOptionsItemSelected(item);
    }
}

When I set a breakpoint here, I confirm that navController is an instance of TestNavHostController.

For completeness, here's my nav_graph.xml:

<?xml version="1.0" encoding="utf-8"?>
<navigation 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"
    android:id="@+id/nav_graph"
    app:startDestination="@id/card_list">

    <fragment
        android:id="@+id/card_list"
        android:name="bbct.android.common.fragment.BaseballCardList"
        android:label="@string/app_name"
        tools:layout="@layout/card_list">
        <action
            android:id="@+id/action_details"
            app:destination="@id/card_details" />
        <action
            android:id="@+id/action_filter"
            app:destination="@id/filter_cards" />
        <argument
            android:name="filterParams"
            app:argType="android.os.Bundle"
            app:nullable="true" />
    </fragment>
    <fragment
        android:id="@+id/about"
        android:name="bbct.android.common.fragment.About"
        android:label="@string/about_title"
        tools:layout="@layout/about" />
    <fragment
        android:id="@+id/card_details"
        android:name="bbct.android.common.fragment.BaseballCardDetails"
        android:label="@string/card_details_title"
        tools:layout="@layout/card_details">
        <argument
            android:name="id"
            app:argType="long" />
    </fragment>
    <fragment
        android:id="@+id/filter_cards"
        android:name="bbct.android.common.fragment.FilterCards"
        android:label="@string/filter_cards_title"
        tools:layout="@layout/filter_cards">
        <action
            android:id="@+id/action_list"
            app:destination="@id/card_list" />
    </fragment>
    <action
        android:id="@+id/action_about"
        app:destination="@id/about" />
</navigation>

Any ideas what I am missing or anything I can try to troubleshoot this test?

Addendum:

Note that my test here uses an ActivityScenario instead of a FragmentScenario as in the example from the documentation. I realize this might be an XY problem. I use a ActivityScenario in this test because I need to click on a MenuItem in the toolbar which is hosted in the Activity. In my previous attempts using a FragmentScenario, the toolbar wasn't rendered. I'm open to suggestions that use a different approach to the test I'm trying to write.


Solution

  • In order to tie navigation destinations to menu items, the IDs of both need to match as per documentation:

    If the id of the MenuItem matches the id of the destination, the NavController can then navigate to that destination.

    This is not exclusive to ActionBar menu, but also to other types of menus like Navigation Drawer & BottomNavigationView menus.

    Without that in place, the navigation won't occur, and the test will fail because the current destination stays unchanged.

    To fix this, both R.id.filter_menu & R.id.filter_cards need to be matched in menu & navGraph XML files, and also in the test, assuming both are filter_cards, then R.id.filter_menu needs to be changed in the menu file and in the test to be R.id.filter_cards:

    Espresso.onView(ViewMatchers.withId(R.id.filter_cards)).perform(ViewActions.click());