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

