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

Android中用PowerMock测试含RxJava的单例类方法遇空指针问题

解决RxJava + PowerMock单例测试中的NPE问题

看起来你遇到的NullPointerException主要来自三个核心问题:静态方法Mock不正确、单例初始化时机未控制,以及IdvManager实例Mock错误。下面一步步帮你修复:

1. 根源分析

你报错的subscribeOn(Schedulers.io())行出现NPE,说明Schedulers.io()返回了null——这是因为你错误地Mock了Schedulers的实例方法,而不是静态方法。另外,IdvManager的Mock也指向了类本身而非Mock实例,导致调用getUserManager()时NPE。

2. 分步修复方案

2.1 修正Schedulers和AndroidSchedulers的静态Mock

代码中调用的是静态方法Schedulers.io()和AndroidSchedulers.mainThread(),你之前Mock了Schedulers实例并设置实例方法的返回值,这完全不对。需要直接Mock静态方法的返回值:

// 在setUp方法中替换原有的Schedulers/AndroidSchedulers Mock逻辑
PowerMockito.mockStatic(Schedulers.class);
PowerMockito.when(Schedulers.io()).thenReturn(testScheduler);

PowerMockito.mockStatic(AndroidSchedulers.class);
PowerMockito.when(AndroidSchedulers.mainThread()).thenReturn(testScheduler);

2.2 修正IdvManager的Mock逻辑

你之前写的PowerMockito.when(IdvManager.getInstance()).thenReturn(IdvManager)是返回类对象,而不是Mock的实例,这会导致后续getUserManager()调用NPE。正确的写法是:

// 在setUp方法中替换原有的IdvManager Mock逻辑
PowerMockito.mockStatic(IdvManager.class);
IdvManager idvManagerMock = PowerMockito.mock(IdvManager.class);
PowerMockito.when(IdvManager.getInstance()).thenReturn(idvManagerMock);
PowerMockito.when(idvManagerMock.getUserManager()).thenReturn(userManager);

2.3 控制UserApi单例的初始化时机

UserApi的SingletonHolder静态内部类会在类加载时自动初始化实例,此时你的Mock可能还未生效,导致依赖未被正确注入。有两种解决方式:

方式一:抑制静态初始化

添加@SuppressStaticInitializationFor注解,阻止UserApi的静态初始化,然后手动创建实例并Spy:

// 在测试类头部添加
@RunWith(PowerMockRunner.class)
@PrepareForTest({IdvManager.class, Base64.class, Schedulers.class, AndroidSchedulers.class, UserApi.class, UUID.class})
@SuppressStaticInitializationFor("com.rahul.manager.UserApi") // 替换为你的UserApi全类名
public class UserApiTest {
    // ... 其他字段

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        testScheduler = new TestScheduler();
        
        // ... 其他Mock逻辑

        // 手动创建UserApi实例并Spy
        userID = UUID.randomUUID().toString();
        UserApi realUserApi = new UserApi(userID);
        spyUserApi = PowerMockito.spy(realUserApi);
    }
}

方式二:Mock UUID静态方法(如果不想抑制初始化)

如果希望保留单例的自动初始化,可以MockUUID.randomUUID()返回固定值,确保单例初始化时的userID可控:

// 在setUp方法中添加
PowerMockito.mockStatic(UUID.class);
userID = UUID.randomUUID().toString();
PowerMockito.when(UUID.randomUUID()).thenReturn(UUID.fromString(userID));

// 此时UserApi.getInstance()会使用我们Mock的UUID值创建实例
spyUserApi = PowerMockito.spy(UserApi.getInstance());

2.4 优化Base64的Mock匹配

为了让Mock更精准,使用eq()匹配Base64的参数:

PowerMockito.mockStatic(Base64.class);
PowerMockito.when(Base64.encodeToString(any(byte[].class), eq(Base64.NO_WRAP)))
           .thenAnswer(invocation -> java.util.Base64.getEncoder().encodeToString((byte[]) invocation.getArguments()[0]));

3. 完整修正后的测试代码示例

@RunWith(PowerMockRunner.class)
@PrepareForTest({IdvManager.class, Base64.class, Schedulers.class, AndroidSchedulers.class, UserApi.class, UUID.class})
@SuppressStaticInitializationFor("com.rahul.manager.UserApi")
public class UserApiTest {
    @Mock
    private UserManager userManager;
    private TestScheduler testScheduler;
    private String userID;
    private UserApi spyUserApi;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        testScheduler = new TestScheduler();

        // Mock IdvManager静态类
        PowerMockito.mockStatic(IdvManager.class);
        IdvManager idvManagerMock = PowerMockito.mock(IdvManager.class);
        PowerMockito.when(IdvManager.getInstance()).thenReturn(idvManagerMock);
        PowerMockito.when(idvManagerMock.getUserManager()).thenReturn(userManager);

        // Mock RxJava调度器
        PowerMockito.mockStatic(Schedulers.class);
        PowerMockito.when(Schedulers.io()).thenReturn(testScheduler);

        PowerMockito.mockStatic(AndroidSchedulers.class);
        PowerMockito.when(AndroidSchedulers.mainThread()).thenReturn(testScheduler);

        // Mock Base64
        PowerMockito.mockStatic(Base64.class);
        PowerMockito.when(Base64.encodeToString(any(byte[].class), eq(Base64.NO_WRAP)))
                   .thenAnswer(invocation -> java.util.Base64.getEncoder().encodeToString((byte[]) invocation.getArguments()[0]));

        // 创建UserApi实例并Spy
        userID = UUID.randomUUID().toString();
        UserApi realUserApi = new UserApi(userID);
        spyUserApi = PowerMockito.spy(realUserApi);
    }

    @Test
    public void verifyUserData_callsPollResult_returnsResponse() {
        // 准备测试数据
        byte[] docFrontArr = "test_doc".getBytes();
        byte[] imgArr = "test_img".getBytes();
        String docStr = java.util.Base64.getEncoder().encodeToString(docFrontArr);
        String imgStr = java.util.Base64.getEncoder().encodeToString(imgArr);

        // Mock UserManager的返回值
        JsonObject verifyResponse = new JsonObject();
        verifyResponse.addProperty("status", "Response created");
        PowerMockito.when(userManager.verifyUserData(docStr, imgStr)).thenReturn(Single.just(verifyResponse));

        // 调用被测方法
        spyUserApi.verifyUserData(docFrontArr, imgArr);
        testScheduler.triggerActions(); // 触发RxJava调度器的任务

        // 验证调用
        Mockito.verify(userManager).verifyUserData(docStr, imgStr);
        // 如果需要验证pollResult被调用,可以添加:
        // PowerMockito.verifyPrivate(spyUserApi).invoke("pollResult");
    }
}

4. 额外注意事项

  • 确保CompositeDisposable在被测方法中被正确初始化(你的代码里已经在verifyUserData里new了,所以没问题)。
  • 如果要验证pollResult()被调用,需要将pollResult()改为protected或package-private,或者用PowerMock的verifyPrivate来验证私有方法:
PowerMockito.verifyPrivate(spyUserApi).invoke("pollResult");

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:45:32