使用launchFragmentInHiltContainer测试时,报addObserver需主线程调用错误
问题原因
JUnit的@Test方法默认在后台线程执行,而TestNavHostController.setGraph()内部会调用LiveData的addObserver方法,该方法必须在主线程执行,因此直接在测试方法中调用setGraph会抛出java.lang.IllegalStateException: Method addObserver must be called on the main thread异常。
解决方案
将navController.setGraph()的调用移到主线程中执行,有两种常用实现方式:
方式一:在测试方法中手动切换到主线程
使用InstrumentationRegistry.getInstrumentation().runOnMainSync()将setGraph操作包裹起来,确保在主线程执行:
@HiltAndroidTest class SensorsTests { @get:Rule var hiltRule = HiltAndroidRule(this) @Inject lateinit var repository: MockRepositoryImpl @BeforeAll fun beforeAll() { hiltRule.inject() } @Test fun userSensorsAreVisible() { val navController = TestNavHostController(ApplicationProvider.getApplicationContext()) // 切换到主线程执行setGraph InstrumentationRegistry.getInstrumentation().runOnMainSync { navController.setGraph(R.navigation.nav_graph) } launchFragmentInHiltContainer<SensorsFragment>(navHostController = navController) // 替换Thread.sleep,用Espresso的等待方法更可靠 Espresso.onView(ViewMatchers.isRoot()).perform(ViewActions.waitForIdle()) } }
方式二:封装到launchFragmentInHiltContainer函数中
修改工具函数,让它内部处理导航图的设置,避免在测试方法中手动切换线程:
inline fun <reified T : Fragment> launchFragmentInHiltContainer( fragmentArgs: Bundle? = null, @StyleRes themeResId: Int = R.style.Theme_SensorsTracker, navGraphId: Int? = null, crossinline action: Fragment.() -> Unit = {} ) { val startActivityIntent = Intent.makeMainActivity( ComponentName( ApplicationProvider.getApplicationContext(), HiltTestActivity::class.java ) ).putExtra( "androidx.fragment.app.testing.FragmentScenario.EmptyFragmentActivity.THEME_EXTRAS_BUNDLE_KEY", themeResId ) ActivityScenario.launch<HiltTestActivity>(startActivityIntent).onActivity { activity -> // 在主线程中创建并配置NavController val navController = navGraphId?.let { TestNavHostController(activity).apply { setGraph(it) } } val fragment: Fragment = activity.supportFragmentManager.fragmentFactory.instantiate( Preconditions.checkNotNull(T::class.java.classLoader), T::class.java.name ) fragment.arguments = fragmentArgs // 监听View生命周期,绑定NavController val lifecycleObserver = Observer<LifecycleOwner?> { viewLifecycleOwner -> if (viewLifecycleOwner != null) { navController?.let { Navigation.setViewNavController(fragment.requireView(), it) } } } fragment.viewLifecycleOwnerLiveData.observeForever(lifecycleObserver) activity.supportFragmentManager .beginTransaction() .add(android.R.id.content, fragment, "") .commitNow() fragment.action() // 移除观察者,避免内存泄漏 fragment.viewLifecycleOwnerLiveData.removeObserver(lifecycleObserver) } }
对应的测试方法可以简化为:
@Test fun userSensorsAreVisible() { launchFragmentInHiltContainer<SensorsFragment>(navGraphId = R.navigation.nav_graph) Espresso.onView(ViewMatchers.isRoot()).perform(ViewActions.waitForIdle()) }
额外优化建议
- 避免使用
Thread.sleep():这种方式不可靠,容易导致测试不稳定,改用Espresso的waitForIdle()或自定义IdlingResource等待界面加载完成。 - 移除LiveData观察者:在工具函数中添加
removeObserver调用,防止内存泄漏。
内容的提问来源于stack exchange,提问作者Captain Jacky
相关产品推荐
相关产品推荐

