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

Jetpack Datastore:如何正确测试IOException处理逻辑?

问题

我们的方案用Preferences Datastore存储部分数据,此前修复了未正确处理IOException的问题。为验证修复有效性,我编写了测试用例,想确认Datastore抛出IOException时的处理逻辑,但测试中IOException未被捕获,直接抛出而非返回NOT_FOUND。想知道测试编写遗漏了什么?是不是因为用了Mock Flow而非真实Flow?

被测代码

class HubConfigurationLocalDataSource @Inject constructor(private val configurationsDataStore: DataStore<Preferences>) {

    private val logger = LoggerFactory.getLogger(this::class.java)

    private object HubConfigurationKeys {
        val CONFIGURATION_LAST_UPDATED = longPreferencesKey("configuration_last_updated")
        val CONFIGURATION_STATUS = stringPreferencesKey("configuration_status")
    }

    val configurationStatus: Flow<HubConfigurationStatusOnRemote> =
        configurationsDataStore.data.catch { exception ->
            when (exception) {
                is IOException -> logger.error(
                    "IOException encountered when retrieving hub configurations.",
                    exception
                )

                else -> logger.error(
                    "retrieval from datastore failed for unknown reason",
                    exception
                )
            }
            emit(emptyPreferences())
        }.map { preferences ->
            val stringFromPreferences = preferences[HubConfigurationKeys.CONFIGURATION_STATUS]
            stringFromPreferences?.let {
                HubConfigurationStatusOnRemote.valueOf(it.uppercase())
            } ?: HubConfigurationStatusOnRemote.NOT_FOUND
        }
}

测试代码

@ExtendWith(MockKExtension::class)
class HubConfigurationLocalDataSourceTest {

    @MockK
    lateinit var mockDataStore: DataStore<Preferences>

    lateinit var sut: HubConfigurationLocalDataSource

    @Test
    fun `If the data store throws an IOException, it is suppressed and Unknown is returned`() = runTest {
        //given
        coEvery { mockDataStore.data} throws IOException()
        sut = HubConfigurationLocalDataSource(mockDataStore)
        //expect
        assertEquals(HubConfigurationStatusOnRemote.NOT_FOUND, sut.configurationStatus.first())
    }
}

测试报错信息

12:46:20.133 [Test worker @kotlinx.coroutines.test runner#2] DEBUG io.mockk.impl.instantiation.AbstractMockFactory - Creating mockk for Flow name=child of mockDataStore#1#2
12:46:20.146 [Test worker @kotlinx.coroutines.test runner#2] DEBUG io.mockk.impl.recording.states.AnsweringState - Throwing java.io.IOException on DataStore(mockDataStore#1).getData()

java.io.IOException
    at com.medixine.devicehub.repository.configuration.datasource.HubConfigurationLocalDataSourceTest$If the data store throws an IOException, it is suppressed and Unknown is returned$1.invokeSuspend(HubConfigurationLocalDataSourceTest.kt:27)
    ...(其余栈信息省略)

解决方案

问题出在测试对DataStore的模拟方式上:

  • 核心原因:DataStore的data属性本身不会抛出异常,真实场景中,IOException是在订阅并收集这个Flow的时候才会抛出的。而你当前的测试是在访问mockDataStore.data这个属性时直接抛出异常,此时Flow的catch操作符还没机会执行,异常就直接扩散出去了。

  • 修复测试代码:需要让mockDataStore.data返回一个在收集时会抛出IOException的Flow,而不是直接抛出异常。修改测试中的coEvery部分:

// 替换原来的throws语句
coEvery { mockDataStore.data } returns flow { throw IOException() }

修改后的完整测试代码:

@ExtendWith(MockKExtension::class)
class HubConfigurationLocalDataSourceTest {

    @MockK
    lateinit var mockDataStore: DataStore<Preferences>

    lateinit var sut: HubConfigurationLocalDataSource

    @Test
    fun `If the data store throws an IOException, it is suppressed and Unknown is returned`() = runTest {
        //given
        coEvery { mockDataStore.data } returns flow { throw IOException() }
        sut = HubConfigurationLocalDataSource(mockDataStore)
        //expect
        assertEquals(HubConfigurationStatusOnRemote.NOT_FOUND, sut.configurationStatus.first())
    }
}

这样修改后,当调用sut.configurationStatus.first()收集Flow时,才会抛出IOException,此时catch操作符就能捕获异常,执行后续逻辑并返回NOT_FOUND,测试就能正常通过。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 17:25:55