浓缩咖啡测试:TestNavHostController.setCurrentDestination 设置不正确
Espresso test: TestNavHostController.setCurrentDestination not being set correctly
在我的 Espresso 测试中,我已设置创建一个带有片段和导航图的 Activity:
protected inline fun <reified A : AppCompatActivity, reified F : Fragment> launchFragment(
@NavigationRes navigationResource: Int,
@IdRes fragmentId: Int
) {
launchFragmentInHiltContainer<A, F> {
navController.setGraph(navigationResource)
navController.setCurrentDestination(fragmentId) //<-- I believe the problem is here
this.viewLifecycleOwnerLiveData.observeForever {
//navController.setCurrentDestination(fragmentId)
Navigation.setViewNavController(this.requireView(), navController)
}
}
}
inline fun <reified A : AppCompatActivity, reified F : Fragment> launchFragmentInHiltContainer(
fragmentArgs: Bundle? = null,
fragmentFactory: FragmentFactory? = null,
crossinline action: Fragment.() -> Unit = {}
) {
val startActivityIntent = Intent.makeMainActivity(
ComponentName(
ApplicationProvider.getApplicationContext(),
A::class.java
)
)
ActivityScenario.launch<A>(startActivityIntent).onActivity { activity ->
fragmentFactory?.let {
activity.supportFragmentManager.fragmentFactory = it
}
val fragment: Fragment = activity.supportFragmentManager.fragmentFactory.instantiate(
Preconditions.checkNotNull(F::class.java.classLoader),
F::class.java.name
)
fragment.arguments = fragmentArgs
activity.supportFragmentManager
.beginTransaction()
.add(android.R.id.content, fragment, "") //<-- I have also tried the Id of the fragment container in the activity
.commitNow()
fragment.action()
}
}
我的 Activity 和 Fragment 加载正常,但如果我记录 findNavController().currentDestination
的结果,它会显示流程中的第一个屏幕(我正在尝试从第 4 个屏幕开始测试)
这不是什么大问题,只是在我的测试设置中片段试图打开一个新片段时出现以下错误:
Caused by: java.lang.IllegalArgumentException: Navigation action/destination packageName:id/action_screen4_to_ErrorScreen cannot be found from the current destination Destination(packageName:id/screen1Fragment) label=screen1 class=screen1Fragment
所以 Navigation
认为我在 startDestination
片段上而不是我要加载的片段,所以我无法在测试中离开我的片段。我不是想弹出,我知道不会有 backstack。
以下代码是我用来在我的片段中导航的代码,它在“流中”运行良好。
findNavController()
.navigate(
R.id.action_screen4_to_ErrorScreen,
null
)
我试过了
- 改为访问
requireView()
- 使用
binding.root.findNavController()
requireActivity().findNavController(requireView().id)
Navigation.findNavController(requireActivity(), R.id.fragmentContainerId)
Navigation.findNavController(requireView())
我创建了一个演示问题的存储库:https://github.com/qbalsdon/espressoTestFramework
所以我发现这是很有可能的,如果您使用深度链接而不是在加载 activity 之后尝试设置目标,则可以将片段加载到任何活动中:
protected inline fun <reified A : AppCompatActivity> launchFragment(
@NavigationRes navigationResource: Int,
@IdRes fragmentId: Int,
fragmentArgs: Bundle? = null
) {
launchFragmentWithDeepLink<A>(fragmentArgs, null, navigationResource, fragmentId)
}
inline fun <reified A : AppCompatActivity> launchFragmentWithDeepLink(
fragmentArgs: Bundle? = null,
fragmentFactory: FragmentFactory? = null,
@NavigationRes navigationResource: Int,
@IdRes fragmentId: Int
) {
val launchIntent = NavDeepLinkBuilder(InstrumentationRegistry.getInstrumentation().targetContext)
.setGraph(navigationResource)
.setComponentName(A::class.java)
.setDestination(fragmentId)
.setArguments(fragmentArgs)
.createTaskStackBuilder().intents[0]
ActivityScenario.launch<A>(launchIntent).onActivity { activity ->
fragmentFactory?.let {
activity.supportFragmentManager.fragmentFactory = it
}
}
}
然后是用法:
@get:Rule(order = 1)
override val hiltRule = HiltAndroidRule(this)
@get:Rule(order = 2)
val activityTestRule = ActivityScenarioRule(MainActivity::class.java)
private fun showFragment() {
launchFragment<MainActivity>(
R.navigation.navigation_graph,
R.id.my_fragment_id
)
}
在我的 Espresso 测试中,我已设置创建一个带有片段和导航图的 Activity:
protected inline fun <reified A : AppCompatActivity, reified F : Fragment> launchFragment(
@NavigationRes navigationResource: Int,
@IdRes fragmentId: Int
) {
launchFragmentInHiltContainer<A, F> {
navController.setGraph(navigationResource)
navController.setCurrentDestination(fragmentId) //<-- I believe the problem is here
this.viewLifecycleOwnerLiveData.observeForever {
//navController.setCurrentDestination(fragmentId)
Navigation.setViewNavController(this.requireView(), navController)
}
}
}
inline fun <reified A : AppCompatActivity, reified F : Fragment> launchFragmentInHiltContainer(
fragmentArgs: Bundle? = null,
fragmentFactory: FragmentFactory? = null,
crossinline action: Fragment.() -> Unit = {}
) {
val startActivityIntent = Intent.makeMainActivity(
ComponentName(
ApplicationProvider.getApplicationContext(),
A::class.java
)
)
ActivityScenario.launch<A>(startActivityIntent).onActivity { activity ->
fragmentFactory?.let {
activity.supportFragmentManager.fragmentFactory = it
}
val fragment: Fragment = activity.supportFragmentManager.fragmentFactory.instantiate(
Preconditions.checkNotNull(F::class.java.classLoader),
F::class.java.name
)
fragment.arguments = fragmentArgs
activity.supportFragmentManager
.beginTransaction()
.add(android.R.id.content, fragment, "") //<-- I have also tried the Id of the fragment container in the activity
.commitNow()
fragment.action()
}
}
我的 Activity 和 Fragment 加载正常,但如果我记录 findNavController().currentDestination
的结果,它会显示流程中的第一个屏幕(我正在尝试从第 4 个屏幕开始测试)
这不是什么大问题,只是在我的测试设置中片段试图打开一个新片段时出现以下错误:
Caused by: java.lang.IllegalArgumentException: Navigation action/destination packageName:id/action_screen4_to_ErrorScreen cannot be found from the current destination Destination(packageName:id/screen1Fragment) label=screen1 class=screen1Fragment
所以 Navigation
认为我在 startDestination
片段上而不是我要加载的片段,所以我无法在测试中离开我的片段。我不是想弹出,我知道不会有 backstack。
以下代码是我用来在我的片段中导航的代码,它在“流中”运行良好。
findNavController()
.navigate(
R.id.action_screen4_to_ErrorScreen,
null
)
我试过了
- 改为访问
requireView()
- 使用
binding.root.findNavController()
requireActivity().findNavController(requireView().id)
Navigation.findNavController(requireActivity(), R.id.fragmentContainerId)
Navigation.findNavController(requireView())
我创建了一个演示问题的存储库:https://github.com/qbalsdon/espressoTestFramework
所以我发现这是很有可能的,如果您使用深度链接而不是在加载 activity 之后尝试设置目标,则可以将片段加载到任何活动中:
protected inline fun <reified A : AppCompatActivity> launchFragment(
@NavigationRes navigationResource: Int,
@IdRes fragmentId: Int,
fragmentArgs: Bundle? = null
) {
launchFragmentWithDeepLink<A>(fragmentArgs, null, navigationResource, fragmentId)
}
inline fun <reified A : AppCompatActivity> launchFragmentWithDeepLink(
fragmentArgs: Bundle? = null,
fragmentFactory: FragmentFactory? = null,
@NavigationRes navigationResource: Int,
@IdRes fragmentId: Int
) {
val launchIntent = NavDeepLinkBuilder(InstrumentationRegistry.getInstrumentation().targetContext)
.setGraph(navigationResource)
.setComponentName(A::class.java)
.setDestination(fragmentId)
.setArguments(fragmentArgs)
.createTaskStackBuilder().intents[0]
ActivityScenario.launch<A>(launchIntent).onActivity { activity ->
fragmentFactory?.let {
activity.supportFragmentManager.fragmentFactory = it
}
}
}
然后是用法:
@get:Rule(order = 1)
override val hiltRule = HiltAndroidRule(this)
@get:Rule(order = 2)
val activityTestRule = ActivityScenarioRule(MainActivity::class.java)
private fun showFragment() {
launchFragment<MainActivity>(
R.navigation.navigation_graph,
R.id.my_fragment_id
)
}