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
相关产品推荐
相关产品推荐

