最近在項目中將Rxjava升級到Rxjava2之后,對之前的P、M(項目是基于MVP結(jié)構(gòu))層進行單元測試的時候,出現(xiàn)了如下的問題:
Android RxJava 2 JUnit test - getMainLooper in android.os.Looper not mocked RuntimeException
java.lang.ExceptionInInitializerError
at io.reactivex.android.schedulers.AndroidSchedulers$1.call(AndroidSchedulers.java:35)
at io.reactivex.android.schedulers.AndroidSchedulers$1.call(AndroidSchedulers.java:33)
at io.reactivex.android.plugins.RxAndroidPlugins.callRequireNonNull(RxAndroidPlugins.java:70)
at io.reactivex.android.plugins.RxAndroidPlugins.initMainThreadScheduler(RxAndroidPlugins.java:40)
at io.reactivex.android.schedulers.AndroidSchedulers.<clinit>(AndroidSchedulers.java:32)
…
Caused by: java.lang.RuntimeException: Method getMainLooper in android.os.Looper not mocked. See http://g.co/androidstudio/not-mocked for details.
at android.os.Looper.getMainLooper(Looper.java)
at io.reactivex.android.schedulers.AndroidSchedulers$MainHolder.<clinit>(AndroidSchedulers.java:29)
...
java.lang.NoClassDefFoundError: Could not initialize class io.reactivex.android.schedulers.AndroidSchedulers
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
出現(xiàn)這個問題的原因是:AndroidSchedulers.mainThread
返回的是LooperScheduler
的一個實例池颈,但是LooperScheduler
依賴于Android,我們在純Java的單元測試中是無法使用它的钓丰。找到了問題的原因解決起來就容易多了躯砰。
我們需要在單元測試運行之前,初始化RxAndroidPlugins
讓它返回一個我們指定的不依賴于Android的Scheduler
携丁。下面給出兩種解決方案琢歇。
方案一:
在@BeforeClass
里面初始化Scheduler
:
@BeforeClass
public static void setUpRxSchedulers(){
final Scheduler immediate = new Scheduler() {
@Override
public Disposable scheduleDirect(@NonNull Runnable run, long delay, @NonNull TimeUnit unit) {
return super.scheduleDirect(run, 0, unit);
}
@Override
public Worker createWorker() {
return new ExecutorScheduler.ExecutorWorker(new Executor() {
@Override
public void execute(@android.support.annotation.NonNull Runnable command) {
command.run();
}
});
}
};
RxJavaPlugins.setInitIoSchedulerHandler(new Function<Callable<Scheduler>, Scheduler>() {
@Override
public Scheduler apply(@NonNull Callable<Scheduler> schedulerCallable) throws Exception {
return immediate;
}
});
RxJavaPlugins.setInitComputationSchedulerHandler(new Function<Callable<Scheduler>, Scheduler>() {
@Override
public Scheduler apply(@NonNull Callable<Scheduler> schedulerCallable) throws Exception {
return immediate;
}
});
RxJavaPlugins.setInitNewThreadSchedulerHandler(new Function<Callable<Scheduler>, Scheduler>() {
@Override
public Scheduler apply(@NonNull Callable<Scheduler> schedulerCallable) throws Exception {
return immediate;
}
});
RxJavaPlugins.setInitSingleSchedulerHandler(new Function<Callable<Scheduler>, Scheduler>() {
@Override
public Scheduler apply(@NonNull Callable<Scheduler> schedulerCallable) throws Exception {
return immediate;
}
});
RxAndroidPlugins.setInitMainThreadSchedulerHandler(new Function<Callable<Scheduler>, Scheduler>() {
@Override
public Scheduler apply(@NonNull Callable<Scheduler> schedulerCallable) throws Exception {
return immediate;
}
});
}
這樣做雖然可以解決問題,但是你需要在每個測試類里面都要添加這樣的代碼梦鉴,非常麻煩李茫,有沒有簡單一點的辦法呢?當(dāng)然是有的尚揣,我們可以自定義一個TestRule
涌矢,在需要的地方直接調(diào)用就行了掖举。
方案二:
自定義一個RxSchedulersOverrideRule
讓它實現(xiàn)TestRule
:
public class RxSchedulersOverrideRule implements TestRule {
private Scheduler immediate = new Scheduler() {
@Override
public Disposable scheduleDirect(@NonNull Runnable run, long delay, @NonNull TimeUnit unit) {
return super.scheduleDirect(run, 0, unit);
}
@Override
public Worker createWorker() {
return new ExecutorScheduler.ExecutorWorker(new Executor() {
@Override
public void execute(@android.support.annotation.NonNull Runnable command) {
command.run();
}
});
}
};
@Override
public Statement apply(final Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
RxJavaPlugins.setInitIoSchedulerHandler(new Function<Callable<Scheduler>, Scheduler>() {
@Override
public Scheduler apply(@NonNull Callable<Scheduler> schedulerCallable) throws Exception {
return immediate;
}
});
RxJavaPlugins.setInitComputationSchedulerHandler(new Function<Callable<Scheduler>, Scheduler>() {
@Override
public Scheduler apply(@NonNull Callable<Scheduler> schedulerCallable) throws Exception {
return immediate;
}
});
RxJavaPlugins.setInitNewThreadSchedulerHandler(new Function<Callable<Scheduler>, Scheduler>() {
@Override
public Scheduler apply(@NonNull Callable<Scheduler> schedulerCallable) throws Exception {
return immediate;
}
});
RxJavaPlugins.setInitSingleSchedulerHandler(new Function<Callable<Scheduler>, Scheduler>() {
@Override
public Scheduler apply(@NonNull Callable<Scheduler> schedulerCallable) throws Exception {
return immediate;
}
});
RxAndroidPlugins.setInitMainThreadSchedulerHandler(new Function<Callable<Scheduler>, Scheduler>() {
@Override
public Scheduler apply(@NonNull Callable<Scheduler> schedulerCallable) throws Exception {
return immediate;
}
});
try {
base.evaluate();
}finally {
RxJavaPlugins.reset();
RxAndroidPlugins.reset();
}
}
};
}
}
在需要的地方直接像下面這樣調(diào)用即可:
@ClassRule
public static RxSchedulersOverrideRule sSchedulersOverrideRule = new RxSchedulersOverrideRule();