如何在仪器测试中启动带有安全参数的片段?
How can I launch a fragment with safe args in an instrumentation test?
下面我有一个测试 class 旨在单独启动片段并测试 navController 的导航能力。
第一次测试,landingToGameFragmentTest()
完美!
第二个测试启动一个片段,该片段依赖于传递给它的安全参数。除此之外,我认为它们的执行方式没有区别。
// 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)
}
我为其参数设置了一个默认值,但在我向它传递一个空包之前,我仍然遇到空参数异常。现在该片段将启动,但它似乎无法导航到任何其他片段!
我在 SO 上找不到类似的问题,堆栈输出超出了我的范围。
在 init(launchFragmentInContainer())
行之后,我单步执行了代码,发现它抛出一个 illegalArgumentException
:
public static int parseInt(@RecentlyNonNull String s, int radix) throws NumberFormatException {
throw new RuntimeException("Stub!");
}
然后导致 getNavigator()
,它传递名称“片段”。
然而,唯一的导航器是“导航”和“测试”,我认为它应该是测试。
然后抛出 illegalStateException
:
/**
* 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
}
最后在 onView(withId(R.id.button_end_game))
上调用 navigate()
生成:
我可能不必在这个特定实例中保留此测试。但是,我将来肯定需要知道如何单独启动片段(这取决于安全参数)。
感谢您的考虑!!
这里有两个完全不同的问题:
您的 Fragment 需要传递参数。
参数通过 launchFragmentInContainer
的 fragmentArgs
参数传递给您的片段,如 Fragment testing guide.
中所述
每个参数 class,例如您的 LandingFragmentArgs
都有一个构造函数,可以让您直接构造 Args
class。然后,您可以使用 toBundle()
方法生成传递给 launchFragmentInContainer
:
的 Bundle
val args = GameFragmentArgs(/* pass in your required args here */)
val bundle = args.toBundle()
init(launchFragmentInContainer<GameFragment>(fragmentArgs = bundle, themeResId = THEME))
您的 NavController 需要将其状态设置为 GameFragment 目的地
您的 TestNavHostController
不知道您的测试需要从与 GameFragment
关联的目的地开始 - 默认情况下,它只会在图表的 startDestination
上(您尝试触发的任何操作都不存在)。
根据 Test Navigation documentation:
TestNavHostController
provides a setCurrentDestination
method that allows you to set the current destination so that the NavController is in the correct state before your test begins.
因此您需要确保在 init
调用之后调用 setCurrentDestination
:
val args = GameFragmentArgs(/* pass in your required args here */)
val bundle = args.toBundle()
val scenario = launchFragmentInContainer<GameFragment>(fragmentArgs = bundle, themeResId = THEME)
init(scenario)
// Ensure that the NavController is set to the expected destination
// using the ID from your navigation graph associated with GameFragment
scenario.onFragment {
// Just like setGraph(), this needs to be called on the main thread
navController.setCurrentDestination(R.id.game_fragment, bundle)
}
下面我有一个测试 class 旨在单独启动片段并测试 navController 的导航能力。
第一次测试,landingToGameFragmentTest()
完美!
第二个测试启动一个片段,该片段依赖于传递给它的安全参数。除此之外,我认为它们的执行方式没有区别。
// 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)
}
我为其参数设置了一个默认值,但在我向它传递一个空包之前,我仍然遇到空参数异常。现在该片段将启动,但它似乎无法导航到任何其他片段!
我在 SO 上找不到类似的问题,堆栈输出超出了我的范围。
在 init(launchFragmentInContainer())
行之后,我单步执行了代码,发现它抛出一个 illegalArgumentException
:
public static int parseInt(@RecentlyNonNull String s, int radix) throws NumberFormatException {
throw new RuntimeException("Stub!");
}
然后导致 getNavigator()
,它传递名称“片段”。
然而,唯一的导航器是“导航”和“测试”,我认为它应该是测试。
然后抛出 illegalStateException
:
/**
* 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
}
最后在 onView(withId(R.id.button_end_game))
上调用 navigate()
生成:
我可能不必在这个特定实例中保留此测试。但是,我将来肯定需要知道如何单独启动片段(这取决于安全参数)。
感谢您的考虑!!
这里有两个完全不同的问题:
您的 Fragment 需要传递参数。
参数通过 launchFragmentInContainer
的 fragmentArgs
参数传递给您的片段,如 Fragment testing guide.
每个参数 class,例如您的 LandingFragmentArgs
都有一个构造函数,可以让您直接构造 Args
class。然后,您可以使用 toBundle()
方法生成传递给 launchFragmentInContainer
:
Bundle
val args = GameFragmentArgs(/* pass in your required args here */)
val bundle = args.toBundle()
init(launchFragmentInContainer<GameFragment>(fragmentArgs = bundle, themeResId = THEME))
您的 NavController 需要将其状态设置为 GameFragment 目的地
您的 TestNavHostController
不知道您的测试需要从与 GameFragment
关联的目的地开始 - 默认情况下,它只会在图表的 startDestination
上(您尝试触发的任何操作都不存在)。
根据 Test Navigation documentation:
TestNavHostController
provides asetCurrentDestination
method that allows you to set the current destination so that the NavController is in the correct state before your test begins.
因此您需要确保在 init
调用之后调用 setCurrentDestination
:
val args = GameFragmentArgs(/* pass in your required args here */)
val bundle = args.toBundle()
val scenario = launchFragmentInContainer<GameFragment>(fragmentArgs = bundle, themeResId = THEME)
init(scenario)
// Ensure that the NavController is set to the expected destination
// using the ID from your navigation graph associated with GameFragment
scenario.onFragment {
// Just like setGraph(), this needs to be called on the main thread
navController.setCurrentDestination(R.id.game_fragment, bundle)
}