上一篇中,基於調研和分析,決定使用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");
複製代碼