Android中用PowerMock测试含RxJava的单例类方法遇空指针问题
看起来你遇到的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

