Тест эспрессо: TestNavHostController.setCurrentDestination не задан правильно

#android #android-espresso #android-architecture-navigation

Вопрос:

В моем тесте Эспрессо я настроил создание действия с фрагментом и навигационным графиком:

 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()
    }
}
 

И моя активность и фрагмент загружаются нормально, но если я зарегистрирую результат 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 фрагменте, а не на фрагменте, который я пытаюсь загрузить, поэтому я не могу уйти от своего фрагмента в тесте. Я не пытаюсь выскочить, я знаю, что не было бы никакого запаса.

Следующий код-это то, что я использую для навигации внутри своего фрагмента, который отлично работает «в потоке».

 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

Комментарии:

1. Каким способом вы ведете журнал findNavController().currentDestination ?

2. В обоих onCreateView и метод, который реагирует на SingleLiveEvent то, что запускает навигацию

3.Ну, ваш observeForever не будет уволен к тому времени onCreateView , как будет вызван — это называется только после onCreateView того, как в соответствии с его документацией. Вы видите то же самое onViewCreated() при использовании binding.root.findNavController() ? Это то, что вы установили, когда делаете Navigation.setViewNavController

4. Да, это то же самое для onViewCreated() . Но теперь он бросил java.lang.IllegalStateException: Fragment did not return a View from onCreateView() or this was called before onCreateView(). — даже несмотря на то, что onCreateView возвращает представление

5. Я в замешательстве, в чем дело? Это не может быть одновременно «одним и тем же», а также где-то создавать исключение. Можете ли вы поделиться своим кодом?

Ответ №1:

Итак, я выяснил, что это вполне возможно, и вы можете загрузить фрагмент в любое действие, если используете глубокую привязку, а не пытаетесь установить место назначения после загрузки действия:

 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
    )
}