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

使用Mockito对SharedPreferences包装器进行单元测试遇NPE问题排查

解决SharedPreferences包装器单元测试的NPE问题

兄弟,你踩的这个坑太常见了——你只Mock了SharedPreferences对象,但忘了Mock它返回的Editor实例!当代码调用sharedPreferences.edit()时,默认Mock的对象会返回null,自然就触发空指针了。下面分两种情况给你解决思路:

一、不用第三方工具:手动Mock接口实现

既然SharedPreferences和Editor都是接口,我们可以自己写一个极简的测试实现类,模拟必要的行为,完全不需要依赖任何框架。

举个例子,你可以写这样的测试实现:

import android.content.SharedPreferences;
import java.util.HashMap;
import java.util.Map;

// 手动Mock的SharedPreferences实现,只实现测试需要的方法
class TestSharedPrefs implements SharedPreferences {
    private final Map<String, Object> storage = new HashMap<>();

    @Override
    public Editor edit() {
        // 返回我们自己Mock的Editor
        return new TestEditor();
    }

    // 按需实现你需要的get方法,比如getString
    @Override
    public String getString(String key, String defValue) {
        return (String) storage.getOrDefault(key, defValue);
    }

    // 其他未用到的方法(比如getInt、registerOnSharedPreferenceChangeListener)可以直接抛出UnsupportedOperationException,或者留空
    @Override
    public int getInt(String key, int defValue) {
        throw new UnsupportedOperationException("Not implemented for test");
    }

    // 内部类实现Editor接口
    class TestEditor implements Editor {
        @Override
        public Editor putString(String key, String value) {
            storage.put(key, value);
            return this; // 链式调用需要返回自身
        }

        @Override
        public boolean commit() {
            return true; // 模拟提交成功
        }

        // 同样,按需实现其他put方法,未用到的可以抛出异常
        @Override
        public Editor putInt(String key, int value) {
            throw new UnsupportedOperationException("Not implemented for test");
        }

        // 其他Editor方法同理...
    }
}

然后在你的测试类里,把包装器依赖的SharedPreferences换成这个TestSharedPrefs实例:

@Test
public void testSaveString() {
    TestSharedPrefs testPrefs = new TestSharedPrefs();
    YourPrefsWrapper wrapper = new YourPrefsWrapper(testPrefs);
    
    wrapper.save("test_key", "test_value");
    // 验证结果是否符合预期
    assertEquals("test_value", testPrefs.getString("test_key", null));
}

这样调用edit()时就会返回我们自己实现的TestEditor,不会再出现NPE了。缺点是如果你的包装器用到了很多SharedPreferences的方法,需要写不少模板代码,但胜在完全无依赖。

二、用工具简化:选择Mockito(Android单元测试标配)

如果觉得手动写实现太繁琐,Mockito是最适合的选择——它是Java/Android生态中最流行的Mock框架,能帮你快速创建Mock对象,并且灵活设置方法的返回值和行为。

步骤1:添加依赖

在你的模块build.gradle(Module级别)里添加Mockito的测试依赖:

dependencies {
    // 单元测试依赖
    testImplementation 'org.mockito:mockito-core:5.+'
    // 如果是Android Instrumented测试,用下面这个
    // androidTestImplementation 'org.mockito:mockito-android:5.+'
}

步骤2:编写测试代码

用Mockito创建SharedPreferences和Editor的Mock实例,并指定edit()返回Mock的Editor:

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.mockito.Mockito.*;

public class YourPrefsWrapperTest {
    @Mock
    private SharedPreferences mockPrefs;
    @Mock
    private SharedPreferences.Editor mockEditor;

    private YourPrefsWrapper wrapper;

    @Before
    public void setUp() {
        // 初始化Mock对象
        MockitoAnnotations.openMocks(this);
        
        // 关键:让mockPrefs的edit()方法返回我们的mockEditor
        when(mockPrefs.edit()).thenReturn(mockEditor);
        
        // 因为Editor是链式调用,所以要设置put方法返回自身
        when(mockEditor.putString(anyString(), anyString())).thenReturn(mockEditor);
        // 设置commit()返回true(模拟提交成功)
        when(mockEditor.commit()).thenReturn(true);
        
        // 初始化你的包装器
        wrapper = new YourPrefsWrapper(mockPrefs);
    }

    @Test
    public void testSaveString() {
        wrapper.save("test_key", "test_value");
        
        // 验证是否正确调用了putString和commit方法
        verify(mockEditor).putString("test_key", "test_value");
        verify(mockEditor).commit();
    }
}

这样一来,当你的包装器调用mockPrefs.edit()时,会返回我们预先Mock好的mockEditor,完全不会出现NPE。而且Mockito还能帮你验证方法的调用情况,让测试更严谨。

总结

  • 如果你坚持不用第三方工具,手动实现SharedPreferences和Editor接口是可行的,适合简单场景;
  • 但大多数情况下,Mockito能大大提升测试效率,是Android单元测试的标准工具,学习成本也很低,非常值得入手。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 04:08:00