上一篇中,基于调研和分析,决定使用Powermock
完成单元测试的编写。java
关于Powermock
的使用方式,网上有不少的文章进行解释,下面仅仅介绍一些在Android
上的经常使用姿式。ide
随着时间推移,该文章会不断完善。单元测试
Powermock提供了mock
和spy
两种方式,对于Activity
的私有方法的调用验证一般须要作方法模拟。mock
和spy
均可以实现,mock
是默认对有方法都模拟。spy
是默认对全部方法都不模拟。测试
我的建议是使用mock
,由于activity
里面的方法逻辑不少,而对于一个单元测试,咱们每每只是测试一个方法,对其它方法都须要mock
。用以验证调用或者模拟方法返回值等。ui
activity
中最不缺的就是控件查找,那么直接调用findViewById()
确定是会报错的Stub
。那么一般的作法是mock
一个activity
,可是mock
的方法的findViewById()
返回值为null。this
举例:验证activity
的onCreate
中是否对View
设置了点击监听。spa
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_launcher);
mSkipView = findViewById(R.id.skip);
mSkipView.setOnClickListener(this);
}
复制代码
测试代码:code
@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));
复制代码
同理,还有获取方法,省略...ip
须要添加加@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");
复制代码