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

在Mockito中模拟带访问器的Kotlin属性:SharedPreferences报错解决

解决PreferenceManager.getDefaultSharedPreferences未Mock的测试报错问题

这个问题我在测试自己的Android应用时也踩过坑——本质是本地单元测试环境下,Android SDK里的很多系统类都是空实现(未Mock),PreferenceManager.getDefaultSharedPreferences()就是典型代表。下面给你几个实用的解决方案,按推荐程度排序:

方案1:重构代码,提升可测试性(最推荐)

直接硬编码调用PreferenceManager会让代码耦合度太高,不利于测试。我们可以把获取SharedPreferences的逻辑抽离出来,方便测试时替换:

第一步:修改你的App类

class YourAppClass(private val context: Context) {
    // 抽离SP获取逻辑,用open修饰方便测试时重写
    protected open fun provideSharedPreferences(): SharedPreferences {
        return PreferenceManager.getDefaultSharedPreferences(context)
    }

    // token属性依赖抽离后的方法
    val token: String?
        get() = provideSharedPreferences().getString("token_key", null)
}

第二步:编写测试类

创建一个测试用的子类,重写provideSharedPreferences方法返回Mock的SP实例:

import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnitRunner
import android.content.SharedPreferences

@RunWith(MockitoJUnitRunner::class)
class YourAppClassTest {

    @Mock
    lateinit var mockSharedPreferences: SharedPreferences

    @Test
    fun token_shouldReturnMockValue() {
        // Mock SP的getString方法,返回你需要的模拟token
        `when`(mockSharedPreferences.getString("token_key", null)).thenReturn("mock_test_token_123")

        // 创建测试用的App子类,注入Mock的SP
        val testableApp = object : YourAppClass(mockContext) {
            override fun provideSharedPreferences(): SharedPreferences {
                return mockSharedPreferences
            }
        }

        // 验证token属性返回预期值
        assert(testableApp.token == "mock_test_token_123")
    }
}

这种方式不需要依赖静态Mock,代码结构更清晰,后续维护也更方便。

方案2:用Mockito-inline Mock静态方法

如果你不想重构代码,可以用Mockito-inline来MockPreferenceManager的静态方法。注意需要先添加依赖:

在你的build.gradle(Module级)中添加:

testImplementation "org.mockito:mockito-core:4.11.0"
testImplementation "org.mockito:mockito-inline:4.11.0" // 必须加这个才能Mock静态方法

然后编写测试代码:

import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnitRunner
import org.mockito.Mockito.mockStatic
import android.preference.PreferenceManager
import android.content.Context
import android.content.SharedPreferences

@RunWith(MockitoJUnitRunner::class)
class YourAppClassTest {

    @Mock
    lateinit var mockContext: Context

    @Mock
    lateinit var mockSharedPreferences: SharedPreferences

    @Test
    fun token_shouldReturnMockValue() {
        // Mock PreferenceManager的静态方法,返回我们的Mock SP
        mockStatic(PreferenceManager::class.java).use { mockedStatic ->
            mockedStatic.`when` {
                PreferenceManager.getDefaultSharedPreferences(mockContext)
            }.thenReturn(mockSharedPreferences)

            // Mock SP的getString方法
            `when`(mockSharedPreferences.getString("token_key", null)).thenReturn("mock_test_token_123")

            // 初始化App类并验证结果
            val app = YourAppClass(mockContext)
            assert(app.token == "mock_test_token_123")
        }
    }
}

mockStatic的use块会自动管理静态Mock的生命周期,避免影响其他测试用例。

方案3:使用AndroidX Test的MockSharedPreferences

AndroidX Test库提供了一个真实的SharedPreferences假实现,适合需要模拟复杂SP操作的场景。

首先添加依赖:

testImplementation "androidx.test:core:1.5.0"
testImplementation "androidx.test.ext:junit:1.1.5"

然后编写测试代码:

import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import androidx.test.core.content.edit

@RunWith(AndroidJUnit4::class)
class YourAppClassTest {

    @Test
    fun token_shouldReturnMockValue() {
        // 获取测试用的Context,创建Mock SP
        val testContext = ApplicationProvider.getApplicationContext<Context>()
        val mockSp = androidx.test.core.content.SharedPreferencesPreferences(testContext, "test_sp")

        // 存入模拟的token值
        mockSp.edit {
            putString("token_key", "mock_test_token_123")
        }

        // 这里需要让你的App类使用这个Mock SP,推荐用依赖注入的方式传入
        // 比如修改App类构造,允许传入SP实例:
        // val app = YourAppClass(testContext, mockSp)
        assert(app.token == "mock_test_token_123")
    }
}

这个方案更贴近真实的SP行为,但需要你的App类支持依赖注入SP实例。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 08:43:23