Rxjava2單元測試的異步和同步轉換

自從接觸到單元測試後,每次看UT的文章開頭就一定會有:單元測試的必要性和好處,這裏我就不累述了。
於是我漸漸入坑,加了相關羣,雖然學習起來比較吃力,但是漸漸也感覺到了好處
我想,可能是單元測試讓我更加理解Android的開發原則,對解耦、依賴理解更加深入。寫好一個好的測試,不僅可以檢測BUG,更能讓你的代碼更完美。
最近對RxJava做UI測試的時候,就出現了這樣的情況,點擊按鈕觸發以下事件,用Retrofit+Rxjava進行網絡請求:

api.getResponse(url, param.getParameters())
                .map(new Function<HttpResult, T>() {
                    @SuppressWarnings("unchecked")
                    @Override
                    public T apply(HttpResult tHttpResult) throws Exception {
                    //代碼省略
                })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread());
                .subscribe(new Observer<T>() {
                    @Override
                    public void onSubscribe(Disposable d) {

                    }

                    @Override
                    public void onNext(T value) {
//                        
                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onComplete() {

                    }
                });

模擬點擊按鈕後RXjava的OnNext事件不觸發,當時我就懵逼了很久,無奈到羣裏發問,雖然沒有得到答案,但是遇到了與我遭遇類似的苦逼騷年。相互討論後,他提到如何讓Rxjava始終處於主線程呢?我隨意的回答:用AndroidSchedulers.mainThread()直接綁定到主線程不就完了。

.subscribeOn(AndroidSchedulers.mainThread())
                        .observeOn(AndroidSchedulers.mainThread());

咦,果然解決了他的問題,測試通過了。無心插柳柳成蔭,這下終於找到問題所在了,原來是異步請求無法正確執行。
還沒等開心,測試到是通過了,可尼瑪我的按鈕是網絡請求,不會讓我在主線程裏面做耗時操作吧,我同意google也不同意啊。
既然這樣不行,那我就分開進行呀,測試的時候同步,正式運行的時候異步不就好了嗎?哈哈,我TM真是天才。

方案1、BaseSchedulerProvider

說幹就幹,首先我們自定義接口類BaseSchedulerProvider,讓測試和正式代碼個實現一下,這樣來控制Scheduler。

public interface BaseSchedulerProvider {

    Scheduler ui();

    Scheduler io();

}
====================================
//正式
final class AppSchedulerProvider implements BaseSchedulerProvider {

    @Override
    public Scheduler ui() {
        return AndroidSchedulers.mainThread();
    }



    @Override
    public Scheduler io() {
        return Schedulers.io();
    }

}
===================================
//測試
public class TestSchedulerProvider implements BaseSchedulerProvider {

    @Override
    public Scheduler ui() {
        return AndroidSchedulers.mainThread();
    }


    @Override
    public Scheduler io() {
        return Schedulers.trampoline();
    }
}

再在presenter裏面注入一個BaseSchedulerProvider

public LoginPresenter(LoginContract.View mView, UserRepository mUserRepository, mHttpRequest mHttpRequest,
                          BaseSchedulerProvider mProvider) {
        this.mView = mView;
        this.mUserRepository = mUserRepository;
        this.mHttpRequest = mHttpRequest;
        this.mProvider=mProvider;
    }

等需要使用的時候就:

                .subscribeOn(provider.io())
                .observeOn(provider.ui())

然後再在測試的時候:

TestSchedulerProvider mProvider=new TestSchedulerProvider();
mLoginPresenter = new LoginPresenter(mView, mUserRepository, mHttpRequest,mProvider);

這樣,測試的時候自然就會調用測試的TestSchedulerProvider,完美的實現了測試和代碼分離。

接下來,重點來了,看看我的測試代碼:

loginActivity=Robolectric.setupActivity(LoginActivity.class);

Button btn_login = (Button) loginActivity.findViewById(R.id.login_btn_login);

btn_login.performClick();
Intent exceptedIntent=new Intent(loginActivity, homeActivity.class);
ShadowActivity shadowActivity=Shadows.shadowOf(loginActivity);
Intent realIntent=shadowActivity.getNextStartedActivity();
        assertEquals(exceptedIntent.getComponent(),realIntent.getComponent());

我才反應過來,我正在UI測試,mLoginPresenter 拿來幹毛啊,一萬隻草泥馬從腦海裏跑過。
不過,用這種方法到是可以很好的解決presenter的單元測試,隨心所欲的轉換線程,我只能這麼安慰自己了。

方案2、RxJavaPlugins

辛苦半天,好像線索又斷了,欲哭無淚啊。毛主席教育我們,打不過就跑!我哪是那種人,哈哈。
當然,又是各種查資料,突然看到了一篇Rxjava1的測試文章:

public static void asyncToSync() {
        Func1<Scheduler, Scheduler> schedulerFunc = new Func1<Scheduler, Scheduler>() {
            @Override
            public Scheduler call(Scheduler scheduler) {
                return Schedulers.immediate();
            }
        };

        RxAndroidSchedulersHook rxAndroidSchedulersHook = new RxAndroidSchedulersHook() {
            @Override
            public Scheduler getMainThreadScheduler() {
                return Schedulers.immediate();
            }
        };

        RxJavaHooks.reset();
        RxJavaHooks.setOnIOScheduler(schedulerFunc);
        RxJavaHooks.setOnComputationScheduler(schedulerFunc);

        RxAndroidPlugins.getInstance().reset();
        RxAndroidPlugins.getInstance().registerSchedulersHook(rxAndroidSchedulersHook);
        }

原來Rxjava已經準備好了API給我們用,直接利用RxAndroidPlugins、RxJavaHooks這兩個類,就可以控制全局,將IO線程直接轉換爲immediate()線程。RX1的方案這裏不多說,想看詳解的請點這裏看原文

這RX1的方案也不適合我啊,不過既然RX1有,那RX2必然會有,跑去看了官方文檔,千辛萬苦(一個四級水平的孩子)的終於看到了希望的曙光:RxJavaPlugins。

    /**
     * 單元測試的時候,利用RxJavaPlugins將io線程轉換爲trampoline
     * trampoline應該是立即執行的意思(待商榷),替代了Rx1的immediate。
     */
    public static void asyncToSync() {
        RxJavaPlugins.reset();
        RxJavaPlugins.setIoSchedulerHandler(new Function<Scheduler, Scheduler>() {
            @Override
            public Scheduler apply(Scheduler scheduler) throws Exception {
                return Schedulers.trampoline();
            }
        });

然後再在測試類里加這麼一句:

@Before
    public void setUp(){
        //將rx異步轉同步
        RxjavaFactory.asyncToSync();
    }

謝天謝地,我終於看到了如下畫面:

這裏寫圖片描述

真是老淚縱橫啊。
其中的trampoline()線程就是RX1的immediate線程;
Rx2裏面已經找不到RxJavaHooks類了,直接使用RxJavaPlugins即可。

感謝觀看,文中如果有錯誤的地方,大家也不要吝嗇,盡情打臉吧,或者有更好的建議,請在下方留言,相互學習。
要源碼的小夥伴們請點擊:https://github.com/mrqatom/atomRXDT-MVP

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