Search code examples
javaandroidtestingandroid-testingandroid-navigation

Test Android Navigation starting from arbitrary location


I have the following navgraph with startDestination as card_list:

<?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" />
    </fragment>
    <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>
</navigation>

Now I want to write a test starting at card_details:

public class NavigationTest {
    @Test
    public void clickOnSaveNavigatesFromDetailsBackToList() {
        TestNavHostController navController = new TestNavHostController(ApplicationProvider.getApplicationContext());
        FragmentScenario<BaseballCardDetails> listScenario = FragmentScenario.launchInContainer(
            BaseballCardDetails.class,
            null,
            R.style.AppTheme
        );
        listScenario.onFragment(fragment -> {
            navController.setGraph(R.navigation.nav_graph);
            Navigation.setViewNavController(fragment.requireView(), navController);
        });
        assertThat(Objects.requireNonNull(navController.getCurrentDestination()).getId())
            .isEqualTo(R.id.card_details);
        BaseballCard card = new BaseballCard(
            true,
            "Mint",
            "Code Guru Apps",
            1993,
            "1",
            50000,
            1,
            "Code Guru",
            "Code Guru Devs",
            "Catcher"
        );
        BBCTTestUtil.sendKeysToCardDetails(card);
        Espresso.onView(ViewMatchers.withId(R.id.save_button)).perform(ViewActions.click());
        assertThat(Objects.requireNonNull(navController.getCurrentDestination()).getId())
            .isEqualTo(R.id.card_list);
    }
}

This fails on

        assertThat(Objects.requireNonNull(navController.getCurrentDestination()).getId())
            .isEqualTo(R.id.card_details);

because the TestNavHostController is on the startDestination of card_list.

The actual failure message is

expected: 2131296377

but was : 2131296378

at bbct.android.common.navigation.NavigationTest.clickOnSaveNavigatesFromDetailsBackToList(NavigationTest.java:116)

  • 2131296377 is the id for card_list
  • 2131296378 is the id for card_details
  • Line 116 is the previously mentioned assertThat()

How do I force this test to start from the correct destination? Or is this not the correct way to write such a test? Am I supposed to write such a test by navigating to card_details first by performing the UI operations that a user would normally do?


Dependencies:

In case it matters, I have the following nav dependency versions:

    navVersion = '2.4.2'
    implementation "androidx.navigation:navigation-fragment:$navVersion"
    implementation "androidx.navigation:navigation-ui:$navVersion"

Solution

  • As per the Testing Navigation guide:

    Just like a real NavController, you must call setGraph to initialize the TestNavHostController. In this example, the fragment being tested was the start destination of our graph. TestNavHostController provides a setCurrentDestination method that allows you to set the current destination (and optionally, arguments for that destination) so that the NavController is in the correct state before your test begins.

    So add a call to setCurrentDestination to initialize your test on a particular destination:

    listScenario.onFragment(fragment -> {
        navController.setGraph(R.navigation.nav_graph);
    
        // Create arguments using Safe Args generated classes
        // or construct the Bundle directly
        BaseballCardDetailsArgs args = new BaseballCardDetailsArgs.Builder(0).build();
        Bundle arguments = args.toBundle();
    
        // Then set the current destination to your card_details
        navController.setCurrentDestination(R.id.card_details, arguments);
    
        Navigation.setViewNavController(fragment.requireView(), navController);
    });