FragmentScenario и вложенные NavHostFragments не выполняют навигацию должным образом в инструментальных тестах.
Я пишу одно приложение Activity, которое использует компоненты навигации Android для помощи в навигации и сценарий фрагмента для инструментального тестирования. Я столкнулся с несоответствием в производительности при использовании кнопки "Назад" между фактическим поведением навигации приложения и поведением фрагмента, тестируемого изолированно во время инструментальных тестов при использовании сценария фрагмента.
В моем MainActivity у меня есть основной NavHostFragment, который занимает весь экран. Я использую этот фрагмент навигационного хоста, чтобы показать несколько экранов, включая некоторые фрагменты основных деталей. Каждый фрагмент основной детали имеет другой NavHostFragment, чтобы показать различные фрагменты деталей для этой функции. Эта установка отлично работает и обеспечивает желаемое мной поведение.
Для создания основного экрана подробных сведений я использую ParentFragment, который имеет два FrameLayout, чтобы создать разделенный экран для планшета, а для телефона я программно скрываю одно из FrameLayout. Когда ParentFragment создается, он определяет, запускается ли он на планшете или телефоне, а затем программно добавляет NavHostFragment в правый макет фрейма на планшете, а на телефоне скрывает правую панель, добавляет NavHostFragment в левую. Для NavHostFragments также установлен другой граф навигации в зависимости от того, запускаются ли они на планшете или телефоне (на телефоне мы показываем фрагменты в виде диалогов, на планшете мы показываем их как обычные фрагменты).
private fun setupTabletView() {
viewDataBinding.framelayoutLeftPane.visibility = View.VISIBLE
if (navHostFragment == null) {
navHostFragment = NavHostFragment.create(R.navigation.transport_destinations_tablet)
navHostFragment?.let {
childFragmentManager.beginTransaction()
.add(R.id.framelayout_left_pane, it, TRANSPORT_NAV_HOST_TAG)
.setPrimaryNavigationFragment(it)
.commit()
}
}
if (childFragmentManager.findFragmentByTag(SummaryFragment.TAG) == null) {
childFragmentManager.beginTransaction()
.add(R.id.framelayout_right_pane, fragFactory.instantiate(ClassLoader.getSystemClassLoader(), SummaryFragment::class.java.canonicalName!!), SummaryFragment.TAG)
.commit()
}
}
private fun setupPhoneView() {
viewDataBinding.framelayoutLeftPane.visibility = View.GONE
if (navHostFragment == null) {
navHostFragment = NavHostFragment.create(R.navigation.transport_destinations_phone)
navHostFragment?.let {
childFragmentManager.beginTransaction()
.replace(R.id.framelayout_left_pane, it, TRANSPORT_NAV_HOST_TAG)
.setPrimaryNavigationFragment(it)
.commit()
}
}
}
При запуске версии приложения devDebug все работает должным образом. Я могу перемещаться с помощью основного NavHostFragment на различные экраны с подробным описанием. После того, как я перейду на экран основных сведений, вложенный NavHostFragment вступает во владение, и я могу перемещаться по экранам внутри и снаружи фрагмента основных сведений с помощью вложенного NavHostFragment.
Когда пользователь пытается нажать кнопку "Назад", что приведет к тому, что он покинет главный экран подробных сведений и перейдет к предыдущему экрану, мы открываем диалоговое окно для пользователя с вопросом, действительно ли он хочет покинуть экран (это экран, на котором они ввести много данных). Для этого мы регистрируем обратный вызов onBackPressDispatcher, чтобы знать, когда была нажата кнопка "Назад", и переходить к диалоговому окну при вызове обратного вызова. В версии devDebug пользователь начинает с местоположения A на навигационном графе. Если, находясь в местоположении A, они нажимают кнопку "Назад", мы показываем фрагмент диалога, спрашивающий, действительно ли пользователь намеревается покинуть экран. Если вместо этого пользователь переходит из местоположения A в местоположение B и щелкает обратно, он сначала перемещается обратно в местоположение A. Если он снова нажимает кнопку возврата,вызывается обратный вызов диспетчера обратного нажатия, и затем им отображается фрагмент диалогового окна, спрашивающий, действительно ли они намерены покинуть местоположение A. Таким образом, кажется, что кнопка возврата влияет на задний стек вложенного NavHostFragment до тех пор, пока у вложенного NavHostFragment не останется только один фрагмент. Когда остался только один фрагмент и нажата кнопка возврата, вызывается обратный вызов onBackPressDisapatcher. Это именно то, что нужно. Однако, когда я пишу инструментальный тест со сценарием фрагмента, в котором я пытаюсь протестировать ParentFragment, я обнаружил, что поведение обратного нажатия отличается. В тесте я использую сценарий фрагмента для запуска ParentFragment, а затем запускаю тест, в котором я выполняю навигацию во вложенном NavHostFragment. Когда я нажимаю кнопку "Назад", я ожидаю, что вложенный фрагмент хоста навигации вытолкнет свой стек. Однако,обратный вызов onBackPressDispatcher вызывается сразу же, а не после того, как в стеке вложенного фрагмента узла навигации останется один фрагмент.
Я установил несколько точек останова в NavHostFragment, и кажется, что при запуске тестов NavHostFragment не настроен для перехвата обратных щелчков. Его метод enableOnBackPressed() всегда вызывается с установленным флагом false.
Я не понимаю, что насчет настройки теста вызывает такое поведение. Я бы подумал, что фрагмент nav host будет перехватывать обратные щелчки, пока в его backstack не останется только один фрагмент, и только тогда будет вызван обратный вызов onBackPressDispatcher.
Я не понимаю, как мне это тестировать? Почему при нажатии кнопки "Назад" вызывается обратный вызов onBackPressDispatcher.
1 ответ
Как видно из FragmentScenario
исходный код, он делает не в настоящее время (по состоянию на Fragment 1.2.1) использованиеsetPrimaryNavigationFragment()
. Это означает, что тестируемый фрагмент не перехватывает кнопку "Назад" и, следовательно, ее дочерние фрагменты (например, вашNavHostFragment
) не перехватывают кнопку назад.
Вы можете сами установить этот флаг в своем тесте:
@Test
fun testParentFragment() {
// Use the reified Kotlin extension to launchFragmentInContainer
with(launchFragmentInContainer<ParentFragment>()) {
onFragment { fragment ->
// Use the fragment-ktx commitNow Kotlin extension
fragment.parentFragmentManager.commitNow {
setPrimaryNavigationFragment(fragment)
}
}
// Now you can proceed with your test
}