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

为何SharedPreferences测试会触发空指针异常导致失败?

SharedPreferences仓库测试空指针问题修复

问题场景

我用MVVM+HILT架构开发项目,给PrefsRepositoryImpl写测试用例时,mock编辑器调用putString触发了NullPointerException,测试失败。

实现类代码

class PrefsRepositoryImpl @Inject constructor(private val sharedPref: SharedPreferences) : PrefsManager {

    override fun saveDataInPrefs(key: String, value: String) {
        with(sharedPref.edit()) {
            putString(key, value)
            apply()
        }
    }
}

测试类代码

class PrefsRepositoryImplTest {

    private lateinit var subject: PrefsRepositoryImpl
    private val sharedPreferences:SharedPreferences = mock()

    @Before
    fun setup(){
        subject = PrefsRepositoryImpl(sharedPreferences)
    }

    @Test
    fun saveDataInPrefs_success(){
        val mockEdit = mock(SharedPreferences.Editor::class.java)
        `when`(mockEdit.putString("testKey","testValue"))
            .thenReturn(mockEdit)
        subject.saveDataInPrefs("testKey","testValue")
        verify(mockEdit, times(1)).putString("testKey","testValue")

    }
}

报错信息

java.lang.NullPointerException
at com.mywebsite.todoapp.data.PrefsRepositoryImpl.saveDataInPrefs(PrefsRepositoryImpl.kt:21)
at com.mywebsite.todoapp.PrefsRepositoryImplTest.saveDataInPrefs_success(PrefsRepositoryImplTest.kt:40)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:110)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:38)
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:62)
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:119)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182)
at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164)
at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:414)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
at java.base/java.lang.Thread.run(Thread.java:829)

问题原因

你只mock了SharedPreferences.Editor,但没有配置mock的SharedPreferences实例返回这个编辑器对象。当代码调用sharedPref.edit()时,默认返回null,进入with块后自然触发空指针。

修复后的测试代码

class PrefsRepositoryImplTest {

    private lateinit var subject: PrefsRepositoryImpl
    private val sharedPreferences: SharedPreferences = mock()

    @Before
    fun setup(){
        subject = PrefsRepositoryImpl(sharedPreferences)
    }

    @Test
    fun saveDataInPrefs_success(){
        val mockEdit = mock(SharedPreferences.Editor::class.java)
        // 关键配置:让SharedPreferences返回我们mock的Editor
        `when`(sharedPreferences.edit()).thenReturn(mockEdit)
        // 链式调用需要返回自身,所以配置putString返回mockEdit
        `when`(mockEdit.putString(anyString(), anyString())).thenReturn(mockEdit)

        subject.saveDataInPrefs("testKey","testValue")

        verify(mockEdit, times(1)).putString("testKey","testValue")
        // 额外验证apply方法是否被调用,确保保存逻辑完整
        verify(mockEdit, times(1)).apply()
    }
}

额外说明

  • 使用anyString()替代硬编码参数,让测试更灵活,符合Mockito最佳实践
  • 别忘了验证apply()方法,这是SharedPreferences保存数据的关键步骤,确保你的代码逻辑完整执行

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 13:45:15