测试ViewModel与Hilt实现时遇observeForever后台线程调用错误求助
问题排查:Cannot invoke observeForever on a background thread
问题描述
我正在测试ViewModel与Hilt的实现,已完成代码搭建,但运行测试时抛出异常:Cannot invoke observeForever on a background thread,请求协助排查问题。
测试类代码
@HiltAndroidTest @UninstallModules(AppModule::class) @OptIn(ExperimentalCoroutinesApi::class) class SimpleViewModelHiltTest { @get:Rule var hiltTestRule = HiltAndroidRule(this) @Inject lateinit var itemsDao: ItemsDao @Inject lateinit var simpleRepository: SimpleRepository lateinit var simpleViewModel: SimpleViewModel @Before fun setUp(){ hiltTestRule.inject() simpleViewModel = SimpleViewModel(simpleRepository) } @After fun tearDown(){ } @Test fun testCaseEmptyItemName_returnsErrorStatus() = runTest{ val itemModel = ItemsModel("",23F,"California",1) simpleViewModel.insertItems(itemModel) val result = simpleViewModel.liveData.getOrAwaitValue() assertThat(result.getContentIfNotHandled()?.status).isEqualTo(Status.ERROR) } @Test fun testCaseEmptyItemPlace_returnsErrorStatus() = runTest { val itemModel = ItemsModel("Banana",23F,"",1) simpleViewModel.insertItems(itemModel) val result = simpleViewModel.liveData.getOrAwaitValue() assertThat(result.getContentIfNotHandled()?.status).isEqualTo(Status.ERROR) } @Test fun testCaseValidValues_returnsSuccessStatus() = runTest { val itemModel = ItemsModel("Banana",23F,"California",1) simpleViewModel.insertItems(itemModel) val result = simpleViewModel.liveData.getOrAwaitValue() assertThat(result.getContentIfNotHandled()?.status).isEqualTo(Status.SUCCESS) } }
错误信息
Cannot invoke observeForever on a background thread
原因及解决办法
原因
runTest默认将测试代码调度到后台线程执行,而getOrAwaitValue()内部调用的observeForever方法要求必须在**主线程(UI线程)**执行,因此触发异常。
解决办法
方法1:添加InstantTaskExecutorRule
这是最便捷的方案,该规则会强制LiveData的所有操作在测试中同步执行,且默认在主线程处理。
- 添加依赖:
testImplementation "androidx.arch.core:core-testing:2.2.0"
- 在测试类中添加规则:
@get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule() @get:Rule var hiltTestRule = HiltAndroidRule(this)
方法2:切换到主线程获取LiveData值
在调用getOrAwaitValue()时,通过withContext切换到主线程调度器:
@Test fun testCaseEmptyItemName_returnsErrorStatus() = runTest{ val itemModel = ItemsModel("",23F,"California",1) simpleViewModel.insertItems(itemModel) val result = withContext(Dispatchers.Main.immediate) { simpleViewModel.liveData.getOrAwaitValue() } assertThat(result.getContentIfNotHandled()?.status).isEqualTo(Status.ERROR) }
方法3:修改自定义getOrAwaitValue扩展函数
如果getOrAwaitValue是自定义扩展函数,可修改它确保观察操作强制在主线程执行:
import android.os.Handler import android.os.Looper import androidx.lifecycle.LiveData import androidx.lifecycle.Observer import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit fun <T> LiveData<T>.getOrAwaitValue(): T { var data: T? = null val latch = CountDownLatch(1) val observer = object : Observer<T> { override fun onChanged(value: T) { data = value latch.countDown() this@getOrAwaitValue.removeObserver(this) } } // 判断当前线程是否为主线程,非主线程则切换执行观察 if (Looper.myLooper() == Looper.getMainLooper()) { observeForever(observer) } else { Handler(Looper.getMainLooper()).post { observeForever(observer) } } // 等待结果,超时时间可按需调整 latch.await(2, TimeUnit.SECONDS) @Suppress("UNCHECKED_CAST") return data as T }
内容的提问来源于stack exchange,提问作者Taki
相关产品推荐
相关产品推荐

