PowerMockito使用方法和避坑指南
2021-01-31 本文已影响0人
Erich_Godsen
常用方法
- 模拟对象
-
PowerMockito.spy(T object)
可用于mock
某个对象,调用该对象某个方法时会根据class
调用其真实方法逻辑,
在不加入
PrepareForTest
的情况下,仅支持non-private & non-final classes
类型的class
mSpyAdapter = PowerMockito.spy(new VideoAdapter());
-
PowerMockito.mock(T object)
模拟某个class
,但是不会调用其具体的方法,但是支持打桩
在不加入
PrepareForTest
的情况下,仅支持non-private & non-final classes
类型的class
BaseViewHolder help = PowerMockito.mock(BaseViewHolder.class);
GlideImageView view = PowerMockito.mock(GlideImageView.class);
PowerMockito.doReturn(view).when(helper).getView(R.id.image);
mSpyAdapter.convert(help, item);
-
PowerMockito.mockStatic(Class<?> type, Class<?>... types)
,对一个类中所有方法进行静态模拟,一般用于工具类上
PowerMockito.mockStatic(RxBus.class);
- 关于抽象类的模拟,注意第二个参数
mSpyAbstractActivity = Mockito.mock(AbstractActivity.class, Mockito.CALLS_REAL_METHODS);
- 查找或设置字段
第一种方式
PowerMockito.field(Class<?> declaringClass, String fieldName)
第二种方式
Whitebox.getField(Class<?> type, String fieldName)
其实上面两种方式殊途同归,最终都是调用第二种方式,如果想要获取就用get
,如果想要设置就用set
//获取字段
IMediaPlayer.OnPreparedListener listener = (IMediaPlayer.OnPreparedListener) field(
IjkVideoView.class, "mPreparedListener").get(mIjkVideoView);
//设置字段
field(IjkVideoView.class, "mVideoHeight").set(mIjkVideoView, 100);
- 查找私有方法
PowerMockito.method(Class<?> declaringClass, String methodName, Class<?>... parameterTypes)
如果要精确查找,可在方法名后面跟上参数列表
//模糊匹配
Method method = method(IjkVideoView.class, "bindSurfaceHolder");
method.invoke(mIjkVideoView, null, null);
//精确查找
method = method(IjkVideoView.class, "bindSurfaceHolder", IMediaPlayer.class, IRenderView.ISurfaceHolder.class);
method.invoke(mIjkVideoView, null, null);
- 回调接口测试
假如下面这种代码,我们想要模拟OnErrorListener
这个接口,该怎么办呢?
mVideoPlay.setOnErrorListener(new IMediaPlayer.OnErrorListener() {
@Override
public boolean onError(IMediaPlayer mp, int what, int extra) {
Log.d(TAG, "onError what=" + what + " extra=" + extra + " feedItem:" + mFeedItem);
return false;
}
});
下面给大家介绍PowerMockito.doAnswer(Answer<?> answer)
这个利器
/**
* Use doAnswer() when you want to stub a void method with generic
* {@link Answer}.
* <p>
* Stubbing voids requires different approach from
* {@link Mockito#when(Object)} because the compiler does not like void
* methods inside brackets...
* <p>
* Example:
* <p>
* <pre>
* doAnswer(new Answer() {
* public Object answer(InvocationOnMock invocation) {
* Object[] args = invocation.getArguments();
* Mock mock = invocation.getMock();
* return null;
* }
* }).when(mock).someMethod();
* </pre>
* <p>
* See examples in javadoc for {@link Mockito} class
*
* @param answer to answer when the stubbed method is called
* @return stubber - to select a method for stubbing
*/
public static PowerMockitoStubber doAnswer(Answer<?> answer) {
return POWERMOCKITO_CORE.doAnswer(answer);
}
使用方法如下:
doAnswer((Answer<Void>) invocation -> {
//拿到设置的接口回调,这个0就是setOnErrorListener这个接口传入的第一个参数
IMediaPlayer.OnErrorListener listener = invocation.getArgument(0);
//下面可以针对不同的情况传入不同的测试参数
listener.onError(null, 0, 0);
return null;
}).when(mVideoPlay).setOnErrorListener(any(IMediaPlayer.OnErrorListener.class));
- 针对构造器中某些需要初始化的方法打桩
例如下面这种情况
public SurfaceRenderView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
//getHolder如果不打桩会一直报空指针异常
getHolder().addCallback(mSurfaceCallback);
getHolder().setType(SurfaceHolder.SURFACE_TYPE_NORMAL);
}
如何打桩呢?需要用到PowerMockito.replace(Method method)
方法
//首先需要找到'getHolder'这个方法,不要找错了,这个方法在父类中
Method method = PowerMockito.method(SurfaceView.class, "getHolder");
PowerMockito.replace(method).with((proxy, method1, args) -> {
//然后返回我们mock的对象
return PowerMockito.mock(SurfaceHolder.class);
});
//然后再调用构造器
mRenderView = PowerMockito.spy(new SurfaceRenderView(PowerMockito.mock(Context.class)));
- 单例类如何打桩
话不多说,直接上代码
mockStatic(SingleInstance.class);
SingleInstance instance = mock(SingleInstance.class);
when(SingleInstance.getInstance()).thenReturn(instance);
最后不要忘了将 SingleInstance.class
加入到PrepareForTest
中
@RunWith(PowerMockRunner.class)
@PrepareForTest({SingleInstance.class})
public class SingleManagerTest {
.........
}
最后需要提一下加入到PrepareForTest
中到副作用,如果当前测试到类就是SingleInstance.class
这个类,那么加入以后,你跑出来的TestResult
报告中该类的覆盖率为零,所以要慎重,加入相关的依赖类没有关系。
- 私有内部类模拟
假如我们写的代码结构如下
public class MainActivity extends AppCompatActivity {
private class NetworkChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.d("action:" + intent.getAction());
}
}
}
在外部类MainActivity
中含有一个私有内部类NetworkChangeReceiver
,此时该如何写单测呢?
@Test
public void testNetworkReceiver() throws Exception {
//找到内部类
Class clazz = Whitebox.getInnerClassType(MainActivity.class, "NetworkChangeReceiver");
//获取构造器,因为是非静态内部类(持有父类引用),默认构造器中需要传入父类对象
Constructor constructor = Whitebox.getConstructor(clazz, MainActivity.class);
BroadcastReceiver receiver = (BroadcastReceiver) constructor.newInstance(mActivity);
clazz.getConstructor(MainActivity.class).newInstance(mActivity);
Intent intent = new Intent();
//根据各种情况传入测试数据
receiver.onReceive(context, intent);
}