上一篇中,基于调研和分析,决定使用Powermock
完成单元测试的编写。
关于Powermock
的使用方式,网上有很多的文章进行解释,下面仅仅介绍一些在Android
上的常用姿势。
随着时间推移,该文章会不断完善。
Powermock提供了mock
和spy
两种方式,对于Activity
的私有方法的调用验证通常需要做方法模拟。mock
和spy
都可以实现,mock
是默认对有方法都模拟。spy
是默认对所有方法都不模拟。
个人建议是使用mock
,因为activity
里面的方法逻辑很多,而对于一个单元测试,我们往往只是测试一个方法,对其它方法都需要mock
。用以验证调用或者模拟方法返回值等。
activity
中最不缺的就是控件查找,那么直接调用findViewById()
肯定是会报错的Stub
。那么通常的做法是mock
一个activity
,但是mock
的方法的findViewById()
返回值为null。
举例:验证activity
的onCreate
中是否对View
设置了点击监听。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_launcher);
mSkipView = findViewById(R.id.skip);
mSkipView.setOnClickListener(this);
}
测试代码:
@PrepareForTest({LauncherActivity.class, Build.VERSION.class})
public class LauncherActivityTest extends PowerMockTest {
@Mock
LauncherActivity activity;
@Mock
View mSkipView;
@Test
public void onCreateSdk19() throws Exception {
PowerMockito.doCallRealMethod().when(activity, "onCreate", ArgumentMatchers.any());
// 当调用findViewById(R.id.skip)时返回mock的View对象
PowerMockito.doReturn(mSkipView).when(activity).findViewById(R.id.skip);
activity.onCreate(null);
// 是否设置监听
Mockito.verify(mSkipView).setOnClickListener(ArgumentMatchers.any());
}
final
和private
字段的赋值。
Whitebox.setInternalState(Build.VERSION.class, "SDK_INT", Integer.valueOf(18));
同理,还有获取方法,省略…
需要添加加@PrepareForTest
Activity
的声明周期方法中通常会有super.onCreate()
方法,但是super
方法又不能执行,一旦执行就会报错Stub
。
PowerMockito.suppress(Whitebox.getMethod(AppCompatActivity.class, "onCreate", Bundle.class));
通过上面的方式,在测试Activity.onCreate()
时,父类的AppCompatActivity
的onCreate()
就不会再执行。
如何验证一个页面跳转呢?
现在常用的方式是通过对于意图的验证,判断是否构造了对应包名的意图。
声明当new Intent()
时,返回mock
的intent
对象。
Intent intent = PowerMockito.mock(Intent.class);
PowerMockito.whenNew(Intent.class).withNoArguments().thenReturn(intent);
PowerMockito.whenNew(Intent.class)
.withParameterTypes(Context.class, Class.class)
.withArguments(ArgumentMatchers.any(Context.class), ArgumentMatchers.any(Class.class))
.thenReturn(intent);
验证
PowerMockito.verifyNew(Intent.class).withArguments(activity, MainActivity.class);
Mockito.verify(intent).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
是否构造了MainActivity
的意图,是否添加了flags
。
当构造一个dialog
时,通常会有如下代码:
private void showExpiredDialog() {
if (mAlertDialog == null || !mAlertDialog.isShowing()) {
mAlertDialog = new AlertDialog.Builder(this)
.setMessage("您的登录状态已经过期,请重新登录")
.setPositiveButton(R.string.confirm, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//
startActivity(AppHelper.makeMainIntent(LogoutDialogActivity.this));
}
})
.create();
mAlertDialog.setCancelable(false);
mAlertDialog.show();
}
}
如何验证setPositiveButton
的OnClickListener
的回调逻辑中的内容是否正确。
@Test
public void logout() throws Exception {
PowerMockito.doCallRealMethod().when(activity, "showExpiredDialog");
// 测试回调逻辑
PowerMockito
.when(builder.setPositiveButton(ArgumentMatchers.anyInt(), ArgumentMatchers.any(DialogInterface.OnClickListener.class)))
.thenAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
// 获取到第二个参数对象
DialogInterface.OnClickListener arg = (DialogInterface.OnClickListener) args[1];
// 直接执行回调
arg.onClick(null, 0);
return invocation.getMock();
}
});
Whitebox.invokeMethod(activity, "showExpiredDialog");
PowerMockito.verifyStatic(AppHelper.class);
AppHelper.makeMainIntent(ArgumentMatchers.any(Context.class));
}
当执行setPositiveButton()
时,会执行Answer
中的回调,此时会直接运行回调方法,然后在验证对应的方法是否执行。
通常一些私有方法需要运行,自己写反射还需要改一些东西。
Whitebox.invokeMethod(activity, "showExpiredDialog");