You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

LiveData单元测试报getMainLooper not mocked错误如何解决

问题描述

在ViewModel中使用LiveData持有响应状态,实现代码如下:

@HiltViewModel
class MainViewModel @Inject constructor(private val repository: Repository): ViewModel() {

    private val _charData = MutableLiveData<Response<List<Character>>>()

    val charData: LiveData<Response<List<Character>>>
        get() = _charData

    init {
        getCharacters()
    }

    fun getCharacters(){
        viewModelScope.launch {
            _charData.value = repository.getCharacters()
        }
    }
}

执行单元测试时持续报错:Method getMainLooper in android.os.Looper not mocked.,报错定位在_charData.value = repository.getCharacters()这一行。按照同类问题的解决方案添加@get:Rule val instantExecutorRule: InstantTaskExecutorRule = InstantTaskExecutorRule()后错误仍然存在,测试代码如下:

@RunWith(JUnit4::class)
class MainViewModelTest {
    @get:Rule
    val mainRule =  MainCoroutineRule()

    lateinit var repository: Repository

    lateinit var viewModel: MainViewModel

    @get:Rule
    val instantExecutorRule: InstantTaskExecutorRule = InstantTaskExecutorRule()

    @Before
    fun setUp(){
        MockitoAnnotations.openMocks(this)
        repository = MockRepository()
        viewModel = MainViewModel(repository)
    }

    @Test
    fun getCharactersSuccess() = runTest {
        val observer = mock<Observer<Response<List<Character>>>>()
        viewModel.getCharacters()
        viewModel.charData.observeForever(observer)
        assertTrue(viewModel.charData.value is Response.Success)
    }
}
修复方案

这个报错由几个常见的测试配置疏漏共同导致,按以下步骤调整即可解决:

  1. 固定Rule执行顺序
    多个@get:Rule同时声明时,JUnit默认不保证执行顺序,一旦协程规则先于InstantTaskExecutorRule生效,LiveData的setValue操作就会提前触发主线程Looper检查,直接抛出你看到的错误。同时你的ViewModel在init块中就会触发请求,必须保证所有规则在ViewModel初始化前完全生效。用RuleChain显式指定规则优先级,保证InstantTaskExecutorRule最先执行:

    @get:Rule
    val ruleChain: TestRule = RuleChain.outerRule(InstantTaskExecutorRule())
        .around(MainCoroutineRule())
    

    删掉原来单独声明的mainRule和instantExecutorRule即可。

    注意核对InstantTaskExecutorRule的导入路径,不要引用过时的android.arch.core包下的类,必须使用androidx.arch.core.executor.testing.InstantTaskExecutorRule,否则规则不会生效。

  2. 调整测试逻辑的执行顺序
    你当前是先调用viewModel.getCharacters()再注册观察者,很容易出现数据发射时执行器还没完成hook的情况。把观察者注册提前到触发请求之前,同时主动等待协程执行完成,测试结束后移除观察者避免内存泄漏:

    @Test
    fun getCharactersSuccess() = runTest {
        val observer = mock<Observer<Response<List<Character>>>>()
        // 先注册观察者
        viewModel.charData.observeForever(observer)
        // 再触发数据请求
        viewModel.getCharacters()
        // 等待所有协程任务执行完成
        advanceUntilIdle()
        assertTrue(viewModel.charData.value is Response.Success)
        // 移除观察者
        viewModel.charData.removeObserver(observer)
    }
    
  3. 补全本地单测配置
    如果你是在src/test目录下做本地JVM单元测试(不是设备上的instrumented测试),需要在模块级build.gradle的android配置块中添加以下配置,避免Android框架存根方法抛出异常:

    android {
        // ... 其他现有配置
        testOptions {
            unitTests.returnDefaultValues = true
        }
    }
    

    同时确认已经引入了必要的测试依赖,版本号和项目中现有lifecycle、协程版本对齐即可:

    testImplementation "androidx.arch.core:core-testing:2.2.0"
    testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4"
    

最后检查你的MainCoroutineRule实现是否符合标准:需要在规则初始化的before回调中把Dispatchers.Main替换为测试用的StandardTestDispatcher,在after回调中还原原有Main dispatcher,否则协程在主线程调度时也会触发Looper相关错误。


内容的提问来源于stack exchange,提问作者Tobi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 10:03:43