Android UI 使用 Hillt 和 NavComponent 进行测试 - NavController 未设置错误

Android UI tests with Hillt and NavComponent - NavController not set error

我一直在尝试为 Android 应用编写一些 UI 测试。我按照官方文档、youtube 教程和 Whosebug 答案进行操作,但我不断收到相同的错误。

Caused by: java.lang.IllegalStateException: View androidx.constraintlayout.widget.ConstraintLayout{f81ae0c V.E...... ........ 0,0-1080,2006 aid=1073741835} does not have a NavController set

我试过使用导航测试库来设置navController,我也试过使用Mockito。我按照 google 示例在场景 observeForever 中设置它。 似乎没有任何效果,因此我们将不胜感激。

我家片段

@AndroidEntryPoint
class HomeFragment : Fragment() {

private lateinit var binding: FragmentHomeBinding
private val viewModel: HomeViewModel by viewModels()

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View {
    binding = FragmentHomeBinding.inflate(inflater, container, false)

    return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    setUpOnClicks()

    binding.homeLoginBtn.setOnClickListener {
        val action = HomeFragmentDirections.actionHomeFragmentToLoginFragment()
        view.findNavController().navigate(action)
    }
}

private fun setUpOnClicks() {
    binding.homeSignUpBtn.setOnClickListener {
        view?.findNavController()?.navigate(R.id.loginFragment)
        }

    binding.homeLoginBtn.setOnClickListener {
        view?.findNavController()?.navigate(R.id.loginFragment)
    }
}
}

我的测试class

@ExperimentalCoroutinesApi
@RunWith(AndroidJUnit4::class)
@HiltAndroidTest
class MainTest {

@get:Rule
var hiltRule = HiltAndroidRule(this)

lateinit var navController: NavController

@Before
fun init() {
    hiltRule.inject()
    navController = Mockito.mock(NavController::class.java)
}

@Test
fun someTest() {
    //val navController = TestNavHostController(ApplicationProvider.getApplicationContext())

    val scenario = launchFragmentInHiltContainer<HomeFragment>() {
        HomeFragment().also { fragment ->
            fragment.viewLifecycleOwnerLiveData.observeForever { viewLifecycleOwner ->
                //if (viewLifecycleOwner != null) {
                navController.setGraph(R.navigation.nav_graph)
                Navigation.setViewNavController(fragment.requireView(), navController)
               // }
            }
        }
    }

    onView(withId(R.id.home_login_btn)).check(matches(isDisplayed()))
    onView(withId(R.id.home_sign_up_btn)).check(matches(isDisplayed()))
    onView(withId(R.id.home_login_btn)).perform(click())
}
}

我的build.gradle

def fragment_version = "1.4.1"
dependencies {

implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

implementation "androidx.fragment:fragment-ktx:$fragment_version"

//Navigation
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"


//testing
// Hilt
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-compiler:$hilt_version"

// For instrumentation tests
androidTestImplementation  "com.google.dagger:hilt-android-testing:$hilt_version"
kaptAndroidTest "com.google.dagger:hilt-compiler:$hilt_version"

debugImplementation "androidx.fragment:fragment-testing:$fragment_version"

def test_version = "1.4.0"
androidTestImplementation "androidx.test:core-ktx:$test_version"
testImplementation "androidx.test:core-ktx:$test_version"
implementation "androidx.test:core:$test_version"

androidTestImplementation "org.mockito:mockito-android:4.5.1"

androidTestImplementation("androidx.navigation:navigation-testing:$nav_version")

和完整错误

Caused by: java.lang.IllegalStateException: View androidx.constraintlayout.widget.ConstraintLayout{f81ae0c V.E...... ........ 0,0-1080,2006 aid=1073741835} does not have a NavController set
at androidx.navigation.Navigation.findNavController(Navigation.kt:71)
at androidx.navigation.ViewKt.findNavController(View.kt:28)
at j.app.uitestswithnav.HomeFragment.onViewCreated$lambda-0(HomeFragment.kt:40)
at j.app.uitestswithnav.HomeFragment.$r8$lambda$ZttjwAPLhhUQTJ71aRb-Z0PLw1I(Unknown Source:0)
at j.app.uitestswithnav.HomeFragment$$ExternalSyntheticLambda0.onClick(Unknown Source:2)
at android.view.View.performClick(View.java:7455)
at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1131)
at android.view.View.performClickInternal(View.java:7432)
at android.view.View.access00(View.java:835)
at android.view.View$PerformClick.run(View.java:28810)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at androidx.test.espresso.base.Interrogator.loopAndInterrogate(Interrogator.java:14)
at androidx.test.espresso.base.UiControllerImpl.loopUntil(UiControllerImpl.java:8)
at androidx.test.espresso.base.UiControllerImpl.loopUntil(UiControllerImpl.java:1)
at androidx.test.espresso.base.UiControllerImpl.injectMotionEvent(UiControllerImpl.java:6)
at androidx.test.espresso.action.MotionEvents.sendUp(MotionEvents.java:7)
at androidx.test.espresso.action.MotionEvents.sendUp(MotionEvents.java:1)
at androidx.test.espresso.action.Tap.sendSingleTap(Tap.java:5)
at androidx.test.espresso.action.Tap.access0(Tap.java:1)
at androidx.test.espresso.action.Tap.sendTap(Tap.java:3)
at androidx.test.espresso.action.GeneralClickAction.perform(GeneralClickAction.java:6)
at androidx.test.espresso.ViewInteraction$SingleExecutionViewAction.perform(ViewInteraction.java:2)
at androidx.test.espresso.ViewInteraction.doPerform(ViewInteraction.java:22)
at androidx.test.espresso.ViewInteraction.access0(ViewInteraction.java:1)
at androidx.test.espresso.ViewInteraction.call(ViewInteraction.java:2)
at androidx.test.espresso.ViewInteraction.call(ViewInteraction.java:1)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7870)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)

当你这样做时:

HomeFragment().also { fragment ->

您正在创建一个全新的片段然后立即将其丢弃 - 它永远不会添加到 FragmentManager。

相反,您需要使用 launchFragmentInHiltContainer 已经为您创建的片段。假设您使用的是相同的 launchFragmentInHiltContainer as the documentation says you should,被测片段已在您的 lambda 中可用,因此您的代码应为:

 val scenario = launchFragmentInHiltContainer<HomeFragment>() {
     // Your HomeFragment under test is already the 'this' in this lambda
     viewLifecycleOwnerLiveData.observeForever { viewLifecycleOwner ->
        navController.setGraph(R.navigation.nav_graph)
        Navigation.setViewNavController(fragment.requireView(), navController)
    }
}

的回复非常完美。今天早上我还找到了另一个解决方案。如果出于任何原因有人想要不同的方法,请遵循以下提交。

它正在将测试 navHostController 传递给 HiltExt class 并将其添加到那里的片段。在调用 setViewNavController

之前,我还必须设置 nav_graph 使其工作

https://github.com/android/architecture-samples/pull/752/files