Below I have a test class designed to launch a fragment in isolation and test the navController's ability to navigate.
The first test, landingToGameFragmentTest()
works perfectly!
The second test launches a fragment that depends on safe args to be passed to it. Aside from that, there is no difference I can perceive in how they are executed.
// Declare navController at top level so it can be accessed from any test in the class
private lateinit var navController: TestNavHostController
// Use Generic type with fragment as upper bound to pass any type of FragmentScenario
private fun <T : Fragment> init(scenario: FragmentScenario<T>) {
// Create a test navController
navController = TestNavHostController(
ApplicationProvider.getApplicationContext()
)
scenario.onFragment { fragment ->
// Link navController to its graph
navController.setGraph(R.navigation.nav_graph)
// Link fragment to its navController
Navigation.setViewNavController(fragment.requireView(), navController)
}
}
@Test
fun landingToGameFragmentTest() {
init(launchFragmentInContainer<LandingFragment>(themeResId = THEME))
// Click button to navigate to GameFragment
onView(withId(R.id.button_start_game))
.perform(click())
assertEquals("Navigation to GameFragment failed",
R.id.gameFragment,
navController.currentDestination?.id)
}
@Test
fun gameToLandingFragmentTest() {
init(launchFragmentInContainer<GameFragment>(themeResId = THEME, fragmentArgs = Bundle()))
onView(withId(R.id.button_end_game))
.perform(click())
assertEquals("Navigation to LandingFragment failed",
R.id.landingFragment,
navController.currentDestination?.id)
}
I set a default value for its arguments, but I still got a null arguments exception until I passed it an empty bundle. Now the fragment will launch but it appears not to be able to navigate to any other fragment!
I could find no similar questions on SO, and the stack output is beyond me.
After the init(launchFragmentInContainer())
line I stepped through the code and found this throwing an illegalArgumentException
:
public static int parseInt(@RecentlyNonNull String s, int radix) throws NumberFormatException {
throw new RuntimeException("Stub!");
}
Which then leads to getNavigator()
, which passes the name "fragment".
However the only navigators are "navigation" and "test", of which I believe it should be test.
The illegalStateException
is then thrown:
/**
* Retrieves a registered [Navigator] by name.
*
* @param name name of the navigator to return
* @return the registered navigator with the given name
*
* @throws IllegalStateException if the Navigator has not been added
*
* @see NavigatorProvider.addNavigator
*/
@Suppress("UNCHECKED_CAST")
@CallSuper
public open fun <T : Navigator<*>> getNavigator(name: String): T {
require(validateName(name)) { "navigator name cannot be an empty string" }
val navigator = _navigators[name]
?: throw IllegalStateException(
"Could not find Navigator with name \"$name\". You must call " +
"NavController.addNavigator() for each navigation type."
)
return navigator as T
}
Finally navigate()
is called on onView(withId(R.id.button_end_game))
generating:
I likely don't have to keep this test in this specific instance. However, I certainly will need to know how to launch a Fragment in isolation (that depends on safe args) in the future.
Thank you for your consideration!!