使用MockK测试Kotlin DataStore时遇parent.path空指针错误的解决方法
问题:DataStore测试中出现"parent.path is null"空指针异常
我正在使用Kotlin的MockK框架编写DataStore的测试类,实现Storage接口的MyStorageClass通过context.dataStore获取数据存储。测试类中Mock了DataStore、Preferences和Context,但运行测试时出现错误:Cannot invoke "String.isEmpty()" because "parent.path" is null。尝试过Mock Preferences、排查异常,但仍无法解决,请问该如何处理?
MyStorageClass代码
open class MyStorageClass(context: Context) : Storage { private val testDataStore = context.dataStore private val dataException: suspend FlowCollector<Preferences>.(cause: Throwable) -> Unit = { exception -> when (exception) { is IOException -> emit(emptyPreferences()) else -> throw exception } } override suspend fun <T> getPreferenceFlow(key: Preferences.Key<T>, defaultValue: T): Flow<T> = testDataStore.data .catch(dataException) .map { preferences -> preferences[key] ?: defaultValue .also { Timber.d("Got preference : ${key.name} : $it") } } }
Storage接口代码
interface Storage { suspend fun <T> getPreferenceFlow(key: Preferences.Key<T>, defaultValue: T): Flow<T> }
测试类代码
class HnavStorageTest { private lateinit var dataStore: DataStore<Preferences> lateinit var preferences: Preferences lateinit var mockContext: Context lateinit var sut: HnavStorage @Before fun setup() { MockKAnnotations.init(this, true) dataStore = mockk(relaxed = true) preferences = mockk(relaxed = true) mockContext = mockk(relaxed = true) sut = HnavStorage(mockContext) } @Test fun `test getPreference Flow`() = runBlocking { coEvery { dataStore.data } returns flowOf(preferences) val key: Preferences.Key<String> = stringPreferencesKey(name = "test_key") val defaultValue = "test_value" val expectedValue = "mocked_value" every { preferences[key] } returns expectedValue val flow = sut.getPreferenceFlow(key, defaultValue) var result: String? = null flow.collect { result = it } assert(result == expectedValue) verify { Timber.d("Get preference value: ${key.name} : $expectedValue") } } }
错误栈信息
Cannot invoke "String.isEmpty()" because "parent.path" is null java.lang.NullPointerException: Cannot invoke "String.isEmpty()" because "parent.path" is null at java.base/java.io.File.<init>(File.java:364) at androidx.datastore.DataStoreFile.dataStoreFile(DataStoreFile.kt:36) at androidx.datastore.preferences.PreferenceDataStoreFile.preferencesDataStoreFile(PreferenceDataStoreFile.kt:38) at androidx.datastore.preferences.PreferenceDataStoreSingletonDelegate$getValue$1$1.invoke(PreferenceDataStoreDelegate.kt:107) at androidx.datastore.preferences.PreferenceDataStoreSingletonDelegate$getValue$1$1.invoke(PreferenceDataStoreDelegate.kt:106) at androidx.datastore.preferences.core.PreferenceDataStoreFactory$create$delegate$1.invoke(PreferenceDataStoreFactory.kt:69)
解决方案
错误原因
Mock的Context没有正确模拟文件路径相关属性,当context.dataStore被调用时,DataStore内部逻辑会尝试创建存储目录,但Mock Context返回的文件路径为null,触发空指针异常。
具体修复步骤
正确Mock Context的文件路径及dataStore实例
在测试的setup方法中,补充模拟filesDir属性,并直接指定context.dataStore返回我们预先Mock好的DataStore实例,避免系统自动创建真实存储:@Before fun setup() { MockKAnnotations.init(this, true) dataStore = mockk(relaxed = true) preferences = mockk(relaxed = true) mockContext = mockk(relaxed = true) // 模拟filesDir,避免路径null问题 val mockFilesDir = mockk<File>(relaxed = true) every { mockFilesDir.path } returns "/tmp/datastore_test" every { mockContext.filesDir } returns mockFilesDir // 直接让mockContext返回我们的dataStore实例 every { mockContext.dataStore } returns dataStore sut = HnavStorage(mockContext) }修正Timber日志的verify匹配内容
测试中verify的日志字符串与MyStorageClass中实际打印的不一致,会导致断言失败,需改为完全匹配:verify { Timber.d("Got preference : ${key.name} : $expectedValue") }可选方案:使用内存DataStore(更可靠)
不想手动Mock文件路径的话,可使用AndroidX提供的DataStore测试依赖,创建内存中的DataStore实例,彻底绕开真实文件操作:// 先在build.gradle添加测试依赖 // testImplementation "androidx.datastore:datastore-preferences-core-testing:1.0.0" // 测试中创建内存DataStore private val testDataStore = PreferenceDataStoreFactory.createInMemoryDataStore()
内容的提问来源于stack exchange,提问作者Satyam Gondhale
相关产品推荐
相关产品推荐

