私はRxJavaを知る過程にあります。私は自分の個人用アプリの1つでそれを使い始めて、コードの単体テストをしたいのですが、いくつかの問題があり、助けを求めています。
シナリオは簡単です。
UserInfo
オブジェクトを取得しますUserInfo
オブジェクトがnullでない場合はtrue
を返し、それ以外の場合はfalse
を返します。上記のシナリオでは、RxJavaコードは次のようになります。
public LiveData<Boolean> doesUserExists(String userName) {
UserExistsObserver observer= new UserExistsObserver ();
getUserInfo(userName).subscribeWith(subscriber);
disposable.add(observer);
return userExists;
}
public Observable<Boolean> getUserInfo(String userName) {
return repository.getUserInfo(userName)
.flatMap(new Function<UserInfo, Observable<Boolean>>() {
@Override
public Observable<Boolean> apply(UserInfo userInfo) throws Exception {
return Observable.just(userInfo != null);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
そのため、getUserInfo()が正しいブール値を返すかどうかを確認する簡単な単体テストを作成したいと思いました。以下は私のユニットテストです。
@Test
public void getUserInfo_returns_true(){
UserInfo userInfo = new UserInfo(); //Dummy data - non null userInfo object
when(repository.getUserInfo("username")).thenReturn(Observable.just(userInfo));
TestObserver<Boolean> testObserver = new TestObserver<>();
//the flatMap operator should return true since userInfo is not null
viewModel.getUserInfo("username").subscribeWith(testObserver);
testObserver.assertValue(true);
}
そして以下は私のログです
Java.lang.AssertionError: Expected: true (class: Boolean), Actual: [] (latch = 1, values = 0, errors = 0, completions = 0)
at io.reactivex.observers.BaseTestConsumer.fail(BaseTestConsumer.Java:163)
at io.reactivex.observers.BaseTestConsumer.assertValue(BaseTestConsumer.Java:328)
at com.ik.githubbrowser.search_user.SearchUserViewModelTest.getUserInfo_returns_true(SearchUserViewModelTest.Java:51)
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)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.Java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.Java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.Java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.Java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.Java:26)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.Java:27)
at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.Java:55)
at org.junit.rules.RunRules.evaluate(RunRules.Java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.Java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.Java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.Java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.Java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.Java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.Java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.Java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.Java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.Java:363)
at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.Java:37)
at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.Java:62)
at org.junit.runner.JUnitCore.run(JUnitCore.Java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.Java:117)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.Java:42)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.Java:262)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.Java:84)
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)
at com.intellij.rt.execution.application.AppMain.main(AppMain.Java:147)
Java.lang.NullPointerException
at io.reactivex.Android.schedulers.HandlerScheduler$HandlerWorker.schedule(HandlerScheduler.Java:70)
at io.reactivex.Scheduler$Worker.schedule(Scheduler.Java:272)
at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.schedule(ObservableObserveOn.Java:161)
at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.onNext(ObservableObserveOn.Java:119)
at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.Java:58)
at io.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarDisposable.run(ObservableScalarXMap.Java:248)
at io.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarXMapObservable.subscribeActual(ObservableScalarXMap.Java:164)
at io.reactivex.Observable.subscribe(Observable.Java:10903)
at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.Java:96)
at io.reactivex.Scheduler$DisposeTask.run(Scheduler.Java:452)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.Java:61)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.Java:52)
at Java.util.concurrent.FutureTask.run(FutureTask.Java:266)
at Java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.Java:180)
at Java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.Java:293)
at Java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.Java:1142)
at Java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.Java:617)
at Java.lang.Thread.run(Thread.Java:745)
Exception in thread "RxCachedThreadScheduler-1" Java.lang.NullPointerException
at io.reactivex.Android.schedulers.HandlerScheduler$HandlerWorker.schedule(HandlerScheduler.Java:70)
at io.reactivex.Scheduler$Worker.schedule(Scheduler.Java:272)
at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.schedule(ObservableObserveOn.Java:161)
at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.onNext(ObservableObserveOn.Java:119)
at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.Java:58)
at io.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarDisposable.run(ObservableScalarXMap.Java:248)
at io.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarXMapObservable.subscribeActual(ObservableScalarXMap.Java:164)
at io.reactivex.Observable.subscribe(Observable.Java:10903)
at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.Java:96)
at io.reactivex.Scheduler$DisposeTask.run(Scheduler.Java:452)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.Java:61)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.Java:52)
at Java.util.concurrent.FutureTask.run(FutureTask.Java:266)
at Java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.Java:180)
at Java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.Java:293)
at Java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.Java:1142)
at Java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.Java:617)
at Java.lang.Thread.run(Thread.Java:745)
Process finished with exit code -1
メソッドは、結果を生成するために2つの異なるスレッドを経由する必要があります(subscribeOn
およびobserveOn
呼び出しのため)。これは、オブザーバーが実際に結果を生成するために時間が必要であることを意味します。オブザーバブルが実際に値を生成したことを確認するには、assertValue
をチェックする前にTestObserver.awaitTerminalEvent()
を使用します。
または、Androidスケジューラーがテスト環境で正しく機能するために追加のコードが必要になる場合があるため、コードをテストするときは異なるスケジューラーを使用する必要があります。
@kiskaeが示唆したように、スケジューラを置き換える必要がありました。 subscribeOn
とobserveOn
の両方のスケジューラーを置き換えました。アイデアは、同じスレッド上で操作を実行して、同期させることです。この単体テストはJVMで実行されるため、JVMはobserveOn
にスケジューラーとして渡されるAndroid特定のAndroidSchedulers.mainThread()
_にアクセスできません。したがって、このスケジューラーはRxAndroidPlugins
クラスの助けを借りてsubscribeOn
クラスを使用してRxJavaPlugins
に渡されたスケジューラーを置き換えるために同じことを行います。
詳細については this 中程度の投稿を参照してください。
以下は私の作業コードです。
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
import org.mockito.runners.MockitoJUnitRunner;
import Java.util.concurrent.Callable;
import io.reactivex.Observable;
import io.reactivex.Scheduler;
import io.reactivex.Android.plugins.RxAndroidPlugins;
import io.reactivex.annotations.NonNull;
import io.reactivex.functions.Function;
import io.reactivex.observers.TestObserver;
import io.reactivex.plugins.RxJavaPlugins;
import io.reactivex.schedulers.Schedulers;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class SearchUserViewModelTest {
private RepositoryImpl repository;
private SearchUserViewModel viewModel;
@BeforeClass
public static void before(){
RxAndroidPlugins.reset();
RxJavaPlugins.reset();
RxJavaPlugins.setIoSchedulerHandler(new Function<Scheduler, Scheduler>() {
@Override
public Scheduler apply(@NonNull Scheduler scheduler) throws Exception {
return Schedulers.trampoline();
}
});
RxAndroidPlugins.setInitMainThreadSchedulerHandler(new Function<Callable<Scheduler>, Scheduler>() {
@Override
public Scheduler apply(@NonNull Callable<Scheduler> schedulerCallable) throws Exception {
return Schedulers.trampoline();
}
});
}
@Before
public void setup(){
MockitoAnnotations.initMocks(this);
repository = mock(RepositoryImpl.class);
viewModel = new SearchUserViewModel(repository);
}
@Test
public void getUserInfo_returns_true(){
UserInfo userInfo = new UserInfo();
userInfo.setName("");
when(repository.getUserInfo(anyString())).thenReturn(Observable.just(userInfo));
TestObserver<Boolean> testObserver = new TestObserver<>();
viewModel.getUserInfo(anyString()).subscribe(testObserver);
testObserver.assertValue(true);
}
@AfterClass
public static void after(){
RxAndroidPlugins.reset();
RxJavaPlugins.reset();
}
}