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

Android单元测试报错:Looper.getMainLooper未Mock问题求助

问题排查:LiveData转Flow测试报错Method getMainLooper in android.os.Looper not mocked

相关代码

Repository实现类

class RepositoryImpl(
        private val service: Service,
        private val dao: Dao,
        private val mainDispatcher: CoroutineDispatcher = Dispatchers.Main,
        ): Repository {

        override suspend fun getDataListFromDbAsync(): Flow<List<Item>> = withContext(mainDispatcher) {
                dao.getDataListAsync().asFlow()
        }

/**Some other code*/

 }

测试类代码

class RepositoryImplTest {

    private val dao: Dao = mockk(relaxed = true)
    private val service: Service = mockk(relaxed = true)
    private val standardCoroutineDispatcher = StandardTestDispatcher()
    private val testCoroutineScope = TestScope(standardCoroutineDispatcher)
    private val repository = RepositoryImpl(service, dao, standardCoroutineDispatcher)
    @get:Rule
    val rule = InstantTaskExecutorRule()

    @BeforeEach
    fun setUp() {
        Dispatchers.setMain(standardCoroutineDispatcher)
    }

    @AfterEach
    fun tearDown() {
        unmockkAll()
        Dispatchers.resetMain()
    }

    @Test
    fun getDataListFromDbAsync() = testCoroutineScope.runTest {
        val dbList = mutableListOf<Item>()
        dbList.add(Item().apply {
            id = 0
            name = "test 0"
            priority = 0
        })
        dbList.add(Item().apply {
            id = 1
            name = "test 1"
            priority = 1
        })
        dbList.add(Item().apply {
            id = 2
            name = "test 2"
            priority = 2
        })

        val mutableLiveData = MutableLiveData<List<Item>>(dbList)
        val liveData: LiveData<List<Item>> = mutableLiveData.value?.let { MutableLiveData(it) }!!

        every { dao.getDataListAsync() } returns liveData

        val resultData = repository.getDataListFromDbAsync().toList().first()

        Assertions.assertEquals(dbList.toList()[0], resultData[0])
    }
}

报错信息

Method getMainLooper in android.os.Looper not mocked. See http://g.co/androidstudio/not-mocked for details.
java.lang.RuntimeException: Method getMainLooper in android.os.Looper not mocked. See http://g.co/androidstudio/not-mocked for details.
    at android.os.Looper.getMainLooper(Looper.java)
    at androidx.arch.core.executor.DefaultTaskExecutor.isMainThread(DefaultTaskExecutor.java:78)
    at androidx.arch.core.executor.ArchTaskExecutor.isMainThread(ArchTaskExecutor.java:108)
    at androidx.lifecycle.LiveData.assertMainThread(LiveData.java:486)
    at androidx.lifecycle.LiveData.observeForever(LiveData.java:224)
    at androidx.lifecycle.FlowLiveDataConversions$asFlow$1$1.invokeSuspend(FlowLiveData.kt:97)
    (Coroutine boundary)
    at androidx.lifecycle.FlowLiveDataConversions$asFlow$1.invokeSuspend(FlowLiveData.kt:96)
    at kotlinx.coroutines.flow.AbstractFlow.collect(Flow.kt:230)
    at kotlinx.coroutines.flow.FlowKt__CollectionKt.toCollection(Collection.kt:26)
    at com.package.name.repository.RepositoryImplTest$getDataListFromDbAsync$1.invokeSuspend(RepositoryImplTest.kt:77)
    at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTestCoroutine$2.invokeSuspend(TestBuilders.kt:208)

问题原因

报错核心是:LiveData的observeForever方法会检查当前线程是否为主线程,JUnit测试环境中Android系统的Looper相关方法未被Mock,导致线程判断逻辑失败。虽然你设置了Coroutine的Main Dispatcher为测试调度器,但ArchTaskExecutor(LiveData内部依赖它判断线程)并没有被替换为测试用执行器,所以依然触发了主线程检查报错。

解决方案

方案1:替换ArchTaskExecutor为测试执行器

在测试类的setUp和tearDown方法中添加逻辑,让LiveData认为当前线程是主线程,无需修改业务代码:

@BeforeEach
fun setUp() {
    Dispatchers.setMain(standardCoroutineDispatcher)
    // 替换ArchTaskExecutor,让LiveData跳过真实主线程检查
    ArchTaskExecutor.getInstance().setDelegate(object : TaskExecutor() {
        override fun executeOnDiskIO(runnable: Runnable) = runnable.run()
        override fun postToMainThread(runnable: Runnable) = runnable.run()
        override fun isMainThread(): Boolean = true
    })
}

@AfterEach
fun tearDown() {
    unmockkAll()
    Dispatchers.resetMain()
    // 恢复原生ArchTaskExecutor
    ArchTaskExecutor.getInstance().setDelegate(null)
}

方案2:直接测试LiveData(避免转Flow)

如果测试目的仅为验证数据正确性,可以使用LiveData的测试扩展,跳过转Flow的步骤:

// 确保依赖androidx.lifecycle:lifecycle-livedata-ktx的测试库
@Test
fun getDataListFromDbAsync() = testCoroutineScope.runTest {
    // 保留原有数据准备逻辑
    val dbList = mutableListOf<Item>().apply {
        add(Item().apply { id=0; name="test 0"; priority=0 })
        add(Item().apply { id=1; name="test 1"; priority=1 })
        add(Item().apply { id=2; name="test 2"; priority=2 })
    }
    val liveData = MutableLiveData<List<Item>>(dbList)
    every { dao.getDataListAsync() } returns liveData

    // 直接测试LiveData
    liveData.test {
        val data = awaitValue()
        Assertions.assertEquals(dbList[0], data[0])
    }
}

方案3:调整Repository的Flow转换逻辑(可选)

若业务逻辑允许,可修改asFlow()的调用,指定不依赖主线程的调度器,但需评估是否符合生产环境的线程约束:

override suspend fun getDataListFromDbAsync(): Flow<List<Item>> = 
    dao.getDataListAsync().asFlow(standardCoroutineDispatcher)

推荐方案

优先选择方案1,它能在不改动业务代码的前提下,让测试环境满足LiveData的线程检查要求,保证测试逻辑与生产逻辑一致。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 11:33:20