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
我一直在尝试为 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
https://github.com/android/architecture-samples/pull/752/files