Android 單元測試實戰(2)—— 基於Powermock的常用方法指南

上一篇中,基於調研和分析,決定使用Powermock完成單元測試的編寫。

關於Powermock的使用方式,網上有很多的文章進行解釋,下面僅僅介紹一些在Android上的常用姿勢。

隨着時間推移,該文章會不斷完善。

Mock vs Spy

Powermock提供了mockspy兩種方式,對於Activity的私有方法的調用驗證通常需要做方法模擬。mockspy都可以實現,mock是默認對有方法都模擬。spy是默認對所有方法都不模擬。

個人建議是使用mock,因爲activity裏面的方法邏輯很多,而對於一個單元測試,我們往往只是測試一個方法,對其它方法都需要mock。用以驗證調用或者模擬方法返回值等。

findViewById

activity中最不缺的就是控件查找,那麼直接調用findViewById()肯定是會報錯的Stub。那麼通常的做法是mock一個activity,但是mock的方法的findViewById()返回值爲null。

舉例:驗證activityonCreate中是否對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());
    }

Whitebox.setInternalState()

finalprivate字段的賦值。

 Whitebox.setInternalState(Build.VERSION.class, "SDK_INT", Integer.valueOf(18));

同理,還有獲取方法,省略…

需要添加加@PrepareForTest

PowerMockito.supress()

Activity的聲明週期方法中通常會有super.onCreate()方法,但是super方法又不能執行,一旦執行就會報錯Stub

PowerMockito.suppress(Whitebox.getMethod(AppCompatActivity.class, "onCreate", Bundle.class));

通過上面的方式,在測試Activity.onCreate()時,父類的AppCompatActivityonCreate()就不會再執行。

PowerMockito.whenNew()

如何驗證一個頁面跳轉呢?

現在常用的方式是通過對於意圖的驗證,判斷是否構造了對應包名的意圖。

聲明當new Intent()時,返回mockintent對象。

 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

PowerMockito.thenAnswer()

當構造一個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();
        }
    }

如何驗證setPositiveButtonOnClickListener的回調邏輯中的內容是否正確。

  @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()

通常一些私有方法需要運行,自己寫反射還需要改一些東西。

Whitebox.invokeMethod(activity, "showExpiredDialog");
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章