从 ActionBar 菜单测试导航

Testing Navigation from ActionBar menu

我正在尝试为菜单项编写测试。这个想法是单击菜单项并验证 NavController 是否设置为正确的目的地:

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);
    }
}

此测试失败

expected: 2131296468

but was : 2131296378

2131296378NavController 中初始片段的 ID。我的测试有什么问题?我最初的想法是我没有正确地注入 TestNavHostController,但是调试验证了 activity 和我预期的一样:

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);
    }
}

当我在这里设置断点时,我确认navControllerTestNavHostController的一个实例。

为了完整起见,这是我的 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>

有什么想法我遗漏了什么或我可以尝试解决此测试的任何问题吗?

附录:

请注意,我这里的测试使用 ActivityScenario 而不是 example from the documentation. I realize this might be an XY problem 中的 FragmentScenario。我在此测试中使用了 ActivityScenario,因为我需要单击 Activity 中托管的工具栏中的 MenuItem。在我之前使用 FragmentScenario 的尝试中,未呈现工具栏。我愿意接受使用不同方法来测试我要编写的测试的建议。

为了将导航目的地绑定到菜单项,两者的 ID 需要匹配 as per documentation:

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

这不仅适用于 ActionBar 菜单,还适用于其他类型的菜单,例如导航抽屉和 BottomNavigationView 菜单。

没有它,导航将不会发生,并且测试将失败,因为当前目的地保持不变。

要解决此问题,R.id.filter_menuR.id.filter_cards 都需要在菜单和 navGraph XML 文件中匹配,并且在测试中也需要匹配,假设两者都是 filter_cards,那么 R.id.filter_menu 需要在菜单文件和测试中更改为 R.id.filter_cards:

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