androidtestingandroid-espressoinstrumented-test

Basic testing NavigationController in Android


I'm currently new to testing, so I decided to start off with some basic stuff.

I handle all my navigations from a DrawerLayout that is connected to an Activity.

So for my testing I launch an ActivityScenarioRule, create a testNavController object and then I set this testNavController to the current view that handles the navigation (The container fragment).

So the test consists on opening the drawer, clicking on menu item(Will navigate to a fragment) and therefore check if navigated to the fragment.

Then I check if that happened, but the testNavController stays on the same destination which is weird because it performs the click, so I decided to check the navController (The real one inside the activity), and it shows me that navigated to the correct fragment.

Here's the needed code:

@LargeTest
@RunWith(AndroidJUnit4::class)
class MapsActivityTest {
    @get:Rule
    var activityScenarioRule = ActivityScenarioRule(MapsActivity::class.java)

    @Test
    fun clickOnDrawerMaps_NavigateToAboutAppFragment() {
        //Create TestNavHostController
        val testNavController = TestNavHostController(ApplicationProvider.getApplicationContext())

        UiThreadStatement.runOnUiThread { // This needed because it throws a exception that method addObserver must be called in main thread
            testNavController.setGraph(R.navigation.nav_graph)
        }
        
        val scenario = activityScenarioRule.scenario
        var navcontroller : NavController? = null
        scenario.onActivity {mapsActivity ->
            navcontroller = mapsActivity.navController //Get the real navController just to debug
            mapsActivity.navController = testNavController //Set the test navController
            Navigation.setViewNavController(mapsActivity.binding.containerFragment, testNavController)
        }

        onView(withId(R.id.drawerLayout)).perform(DrawerActions.open()).check(matches(isOpen()))
        onView(withId(R.id.aboutAppFragment)).perform(click())
        assertThat(testNavController.currentDestination?.id).isEqualTo(R.id.aboutAppFragment)
    }
}

In the example they use a Fragment, which they set the fragment.requireView() on the launch of the fragment, but I think it's exactly the same.

What am I doing wrong here?


Solution

  • When you use ActivityScenario (or ActivityScenarioRule), your activity is brought all the way up to the resumed state before any onActivity calls are made. This means that your real NavController has already been created and used when you call setupWithNavController. This is why your call to setViewNavController() has no effect.

    For these types of integration tests (where you have a real NavController), you should not use TestNavHostController.

    As per the Test Navigation guide, TestNavHostController is designed for unit tests where you do not have any real NavController at all, such as when testing one fragment in isolation.