I've been trying to write some UI tests for Android app. I followed official docs, youtube tutorials and stackoverflow answers and I keep getting the same error.
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
I tried using navigation test library to set navController, I tried using Mockito as well. I followed google example to set it in scenarios observeForever. Nothing seems to work so any help would be appreciated.
my home fragment
@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)
}
}
}
my Test 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())
}
}
my 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")
and the full error
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.access$3700(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.access$100(Tap.java:1)
at androidx.test.espresso.action.Tap$1.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.access$100(ViewInteraction.java:1)
at androidx.test.espresso.ViewInteraction$1.call(ViewInteraction.java:2)
at androidx.test.espresso.ViewInteraction$1.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)
CodePudding user response:
When you do:
HomeFragment().also { fragment ->
You're creating a brand new fragment then immediately throwing it away - it is never added to the FragmentManager.
Instead, you need to use the Fragment that launchFragmentInHiltContainer
has already created for you. Assuming you are using the same launchFragmentInHiltContainer
as the documentation says you should, the Fragment under test is already available in your lambda, so your code should read:
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)
}
}
CodePudding user response:
Response from ianhanniballake works perfect. I also found another solution this morning. If for any reason someone wants different approach follow the below commit.
It is passing test navHostController to the HiltExt class and adding it to the fragment there. I also had to set the nav_graph to make it work before calling setViewNavController
https://github.com/android/architecture-samples/pull/752/files