【问题标题】:Unit test singleton class method in android using PowerMock使用PowerMock在android中单元测试单例类方法
【发布时间】:2019-11-19 13:41:03
【问题描述】:

我需要对内部使用 RxJava Singles 的 Singleton 类的方法进行单元测试,并使用 PowerMock 测试框架来模拟静态类和方法。我尝试了各种方法来模拟 Schedulers.io() 和 AndroidSchedulers.mainThread() 方法,但它不起作用。我在 UserApi.verifyUserData() 方法中的 .subscribeOn(Schedulers.io()) 行收到 java.lang.NullPointerException 错误。

单例类 UserApi(被测类)

final public class UserApi {
    private CompositeDisposable compositeDisposable;
    private String userID; 
    //private final SchedulerProvider schedulerProvider;

    private UserApi(String userId) {
        super();
        this.userID = userId;
        //this.schedulerProvider = schedulerProvider;
    }

    public static UserApi getInstance() {
        return SingletonHolder.sINSTANCE;
    }

    private static final class SingletonHolder {
        private static final UserApi sINSTANCE;

        static {
            String uuid = UUID.randomUUID().toString();
            sINSTANCE = new UserApi(uuid);
        }
    }

    // Rest Api call

    public void verifyUserData(byte[] doc, byte[] img) {
        this.compositeDisposable = new CompositeDisposable();
        String docStr = Base64.encodeToString(doc, Base64.NO_WRAP);
        String imgStr = Base64.encodeToString(img, Base64.NO_WRAP);

        final Disposable apiDisposable = IdvManager.getInstance().getUserManager().verifyUserData(docStr, imgStr)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<JsonObject>() {
                    @Override
                    public void accept(JsonObject verifyResponse) throws Exception {
                        pollResult();
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable error) throws Exception {
                      // handle error code...
                    }
                });
        this.compositeDisposable.add(apiDisposable);
    }

    private void pollResult() {
        // code here...
    }

}

UserManager 类和接口

public interface UserManager {

    Single<JsonObject> verifyUserData(String docStr, String imgStr);

}

final class UserManagerImpl implements UserManager {

    private final UserService userService;

    UserManagerImpl(final Retrofit retrofit) {
        super();
        this.userService = retrofit.create(UserService.class);
    }
    @Override
    public Single<JsonObject> verifyUserData(String docStr, String imgStr) {
     // Code here...
    }
}

单元测试

@RunWith(PowerMockRunner.class)
@PrepareForTest({IdvManager.class, Base64.class, Schedulers.class, AndroidSchedulers.class, UserApi.class})
public class UserApiTest {

    @Mock
    public UserManager userManager;
    @Mock
    private Handler handler;

    private IdvManager idvManager;
    private Schedulers schedulers;

    private UserApi spyUserApi;
    private TestScheduler testScheduler;
    private String userID;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        testScheduler = new TestScheduler();
        handler = new Handler();
        PowerMockito.suppress(constructor(IdvManager.class));
        // mock static
        PowerMockito.mockStatic(IdvManager.class);
        PowerMockito.mockStatic(Schedulers.class);
        PowerMockito.mockStatic(AndroidSchedulers.class);
        PowerMockito.mockStatic(Base64.class);
        // Create mock for class
        idvManager = PowerMockito.mock(IdvManager.class);
        schedulers = PowerMockito.mock(Schedulers.class);
        PowerMockito.when(IdvManager.getInstance()).thenReturn(IdvManager);
        when(idvManager.getUserManager()).thenReturn(userManager);

        spyUserApi = PowerMockito.spy(UserApi.getInstance());
        // TestSchedulerProvider testSchedulerProvider = new TestSchedulerProvider(testScheduler);

        when(Base64.encodeToString((byte[]) any(), anyInt())).thenAnswer(new Answer<Object>() {
            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                return java.util.Base64.getEncoder().encodeToString((byte[]) invocation.getArguments()[0]);
            }
        });

        when(schedulers.io()).thenReturn(testScheduler);
        when(AndroidSchedulers.mainThread()).thenReturn(testScheduler);
        userID = UUID.randomUUID().toString();
    }

    @After
    public void clearMocks() {
        //Mockito.framework().clearInlineMocks();
    }

    @Test
    public void verifyUserData_callsPollResult_returnsResponse() {
        // Input
        String docStr = "iVBORw0KGgoAAAANSUhEUgAAAJ4AAACeCAYAAADDhbN7AA.....";
        // Output
        JsonObject verifyResponse = new JsonObject();
        verifyResponse.addProperty("status", "Response created");
        doReturn(Single.just(verifyResponse)).when(userManager).verifyUserData(docStr, docStr);
        // spy method call
        spyUserApi.verifyUserData(docFrontArr, docFrontArr);
        testScheduler.triggerActions();
        // assert
        verify(userManager).verifyUserData(docStr, docStr);

    }
}

错误

java.lang.NullPointerException
    at com.rahul.manager.UserApi.verifyUserData(UserApi.java:60)
    at com.rahul.manager.UserApiTest.verifyUserData_callsPollResult_returnsResponse(UserApiTest.java:171)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)

我不确定是否可以通过使用 PowerMock 监视 Singleton 类的真实实例来测试 Singleton 类的方法。

【问题讨论】:

  • 尝试在prepare注解中添加UserAPI.class
  • @Sergio 我已经添加它实际上在发布时确实出现了拼写错误,现在已更正。
  • UserManager 是您的课程之一吗?如果您所说的是正确的,那么verifyUserData 将返回null,这要么是由于模拟的参数定义不匹配,要么因为变量userManagernull。您可能想删除与您的课程和测试无关的任何内容,然后您可以发布minimal reproducible example
  • @second UserManger 是由相应类实现的接口,我已经模拟了相同的,还在上面的帖子中添加了所需的代码。所以这不会造成任何问题。实际上,通过 RxJava Single 方法调用 Schedulers.io() 方法时会出现 nullPointerException 错误。我不确定在调用 UserApi.getInstance() 方法后是否可以通过监视 UserApi 的真实实例来测试单例类方法。

标签: java android mockito rx-java powermock


【解决方案1】:

测试您的代码很复杂,因为它不可测试且不可扩展。它在任何地方都包含硬编码的依赖项(例如用户 ID、处理程序、几个单例)。
如果您决定使用其他 id 生成方法或其他处理程序,您将无法在不重写整个类的情况下执行此操作。
不要硬编码依赖项,而是在构造函数(对于强制依赖项)或设置器(对于可选依赖项)中请求它们。
这将使您的代码具有可扩展性和可测试性。完成此操作后,您将看到您的类包含多个职责,将它们移动到单独的类后,您会得到更好的图片:-)

例如:

public UserApi(String userId, Handler handle) {
    this.userId = userId;
    this.handler = handler;
}

【讨论】:

  • 这似乎是评论而不是答案。
  • @nickolay 我已经删除了处理程序和其他额外的代码,并在上面的帖子中更新了相同的内容。我的问题有所不同,这是因为 RxJava Single 的 subscribeOn() 方法中传递了 Schedulers.io()。我不确定是否可以在调用 UserApi.getInstance() 方法后通过监视 UserApi 类的真实实例来测试单例类方法。
  • @RahulKumar 我看到的这段代码的问题是单个类中有太多内容。单例也不错,但是以隐藏的方式调用它们会使测试和扩展这个类变得非常困难。理想情况下,我们的方法中不应使用“new”关键字。 UserApi#verifyUserData 方法需要 UserManager。而不是自己从单例或一些静态的东西中获取它,只需在 UserApi 构造函数中请求它。传递正确的 UserManager 实例是 UserApi 用户的责任。有关详细信息,请参阅 misko.hevery.com/code-reviewers-guide Misko 教 Google 员工使用 TDD。
【解决方案2】:

Schedulers.io() 是一个静态方法,所以你需要使用mockStatic(你做了)并相应地定义相关的模拟。

我稍微重新安排了您的setup 方法,以提高可读性并修复了错误。您不需要Schedulers 的实例(您命名为schedulers 的变量)。

可能是您犯了一个简单的错字,因为您为 Base64AndroidSchedulers 做了正确的事情。

@Before
public void setUp() {

    MockitoAnnotations.initMocks(this);

    testScheduler = new TestScheduler();
    handler = new Handler();

    // mock for instance of `IdvManager` 
    PowerMockito.suppress(constructor(IdvManager.class));
    idvManager = PowerMockito.mock(IdvManager.class);
    when(idvManager.getUserManager()).thenReturn(userManager);

    // mock for `IdvManager` class
    PowerMockito.mockStatic(IdvManager.class);
    PowerMockito.when(IdvManager.getInstance()).thenReturn(idvManager);

    // mock for `Schedulers` class
    PowerMockito.mockStatic(Schedulers.class);
    when(Schedulers.io()).thenReturn(testScheduler);

    // spy for instance of `UserApi`
    spyUserApi = PowerMockito.spy(UserApi.getInstance());

    // mock for `Base64` class
    PowerMockito.mockStatic(Base64.class);
    when(Base64.encodeToString((byte[]) any(), anyInt())).thenAnswer(new Answer<Object>() {
        @Override
        public Object answer(InvocationOnMock invocation) throws Throwable {
            return java.util.Base64.getEncoder().encodeToString((byte[]) invocation.getArguments()[0]);
        }
    });

    // mock for `AndroidSchedulers` class
    PowerMockito.mockStatic(AndroidSchedulers.class);
    when(AndroidSchedulers.mainThread()).thenReturn(testScheduler);

    userID = UUID.randomUUID().toString();
}

但是,NPE 缺少实际表明其失败的部分,如果这不能解决您的问题,请考虑添加它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-29
    • 2018-10-23
    相关资源
    最近更新 更多