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

使用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,触发空指针异常。

具体修复步骤

  1. 正确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)
    }
    
  2. 修正Timber日志的verify匹配内容
    测试中verify的日志字符串与MyStorageClass中实际打印的不一致,会导致断言失败,需改为完全匹配:

    verify { Timber.d("Got preference : ${key.name} : $expectedValue") }
    
  3. 可选方案:使用内存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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 16:25:11