web-dev-qa-db-ja.com

AndroidユニットテストでのモックレトロフィットObservable <T>応答

私はAPIインターフェースを持っており、ネットワーク呼び出しを含むViewをテストしています。

_@Config(emulateSdk = 18)
public class SampleViewTest extends RobolectricTestBase {

    ServiceApi apiMock;

    @Inject
    SampleView fixture;

    @Override
    public void setUp() {
        super.setUp(); //injection is performed in super
        apiMock = mock(ServiceApi.class);
        fixture = new SampleView(activity);
        fixture.setApi(apiMock);
    }

    @Test
    public void testSampleViewCallback() {
        when(apiMock.requestA()).thenReturn(Observable.from(new ResponseA());
        when(apiMock.requestB()).thenReturn(Observable.from(new ResponseB());

        AtomicReference<Object> testResult = new AtomicReference<>();
        fixture.updateView(new Callback() {

            @Override
            public void onSuccess(Object result) {
                testResult.set(result);
            }

            @Override
            public void onError(Throwable error) {
                throw new RuntimeException(error);
            }
        });

        verify(apiMock, times(1)).requestA();
        verify(apiMock, times(1)).requestB();

        assertNotNull(testResult.get());
    }
}
_

何らかの理由で、apiMockメソッドが呼び出されることはなく、検証は常に失敗します。

私の見解では、私は自分のAPIをこのように呼んでいます

_apiV2.requestA()
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Observer());
_

ここで何が欠けていますか?

更新#1:
調査の結果、私の実装(上記のサンプル)では、observeOn(AndroidSchedulers.mainThread())サブスクライバーが呼び出されていないようです。まだ理由はわかりません。

更新#2:
そのようにサブスクライブすると、apiV2.requestA().subscribe(new Observer());すべてが正常に機能します-モックAPIが呼び出され、テストに合格します。
ShadowLooper.idleMainLooper(5000)を進めると何も起こりませんでした。 HandlerThreadSchedulerのハンドラーからルーパーを取得し、それを進めました。同じ結果。

更新#3: APIが使用される実際のコードを追加します。

_public void updateView(final Callback) {
    Observable.Zip(wrapObservable(api.requestA()), wrapObservable(api.requestB()),
        new Func2<ResponseA, ResponseB, Object>() {
            @Override
            public Object call(ResponseA responseA, ResponseB responseB) {
                return mergeBothResponses(responseA, responseB);
            }
        }
    ).subscribe(new EndlessObserver<Object>() {

        @Override
        public void onError(Throwable e) {
            Log.e(e);
            listener.onError(e);
        }

        @Override
        public void onNext(Object config) {
            Log.d("Configuration updated [%s]", config.toString());
            listener.onSuccess(config);
        }
    });
}

protected <T> Observable<T> wrapObservable(Observable<T> observable) {
    return observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}
_
18
Martynas Jurkus

私はまだrxjavaを自分で適切に使用する方法に頭を悩ませていますが、元の要求応答のObservableの両方で実行するのではなく、最後のzip形式のObservableでのみobserveOn(mainThread)を実行するようにコードを変更しようとします。次に、これが両方のルーパーを進める必要があるという事実に影響するかどうかを確認します。

テストを単純化し、ルーパーのアイドリングの必要性を排除するために、テストを実行するときにバックグラウンド処理を必要としないため、方程式からスレッドを削除します。これを行うには、スケジューラーを静的に作成する代わりに、スケジューラーを注入します。プロダクションコードを実行するときは、AndroidSchedulers.mainThreadとSchedulers.ioを挿入し、テストコードを実行するときは、該当する場合はSchedulers.immediateを挿入します。

@Inject
@UIScheduler /* Inject Schedulers.immediate for tests and AndroidSchedulers.mainThread for production code */
private Scheduler mainThreadSched;

@Inject
@IOScheduler /* Inject Scheduler.immediate for tests and Schedulers.io for production code */
private Scheduler ioSched;

public void updateView(final Callback) {
    Observable.Zip(wrapObservable(api.requestA()), wrapObservable(api.requestB()),
        new Func2<ResponseA, ResponseB, Object>() {
            @Override
            public Object call(ResponseA responseA, ResponseB responseB) {
                return mergeBothResponses(responseA, responseB);
            }
        }
    ).observeOn(mainThreadSched)
    .subscribe(new EndlessObserver<Object>() {

        @Override
        public void onError(Throwable e) {
            Log.e(e);
            listener.onError(e);
        }

        @Override
        public void onNext(Object config) {
            Log.d("Configuration updated [%s]", config.toString());
            listener.onSuccess(config);
        }
    });
}

protected <T> Observable<T> wrapObservable(Observable<T> observable) {
    return observable.subscribeOn(ioSched);
}
14
Miguel Lavigne

どのバージョンのrxjavaを使用していますか? ExecutorSchedulerに関して0.18。*バージョンにいくつかの変更があったことを知っています。 0.18.3を使用しているときに、サブスクリプションが事前にサブスクリプション解除されるため、onCompleteメッセージが表示されないという同様の問題が発生しました。私があなたにこれについて言及している唯一の理由は、0.19.0の修正が私の問題を修正したということです。

残念ながら、修正内容の詳細を説明することはできません。現時点では理解できませんが、同じ原因であることが判明した場合は、もっと理解している人が説明できるかもしれません。これが私が話していることのリンクです https://github.com/Netflix/RxJava/issues/1219

これはあまり答えではありませんが、それがあなたを助けることができる場合に備えてもっと注意が必要です。

1
Miguel Lavigne

@ champ016が述べたように、0.19.0より前のRxJavaバージョンに問題がありました。

0.19.0を使用する場合、次のアプローチが機能します。なぜ私が両方のルーパーを進めなければならないのかまだよくわかりませんが。

@Test
public void testSampleViewCallback() {
    when(apiMock.requestA()).thenReturn(Observable.from(new ResponseA());
    when(apiMock.requestB()).thenReturn(Observable.from(new ResponseB());

    AtomicReference<Object> testResult = new AtomicReference<>();
    fixture.updateView(new Callback() {

        @Override
        public void onSuccess(Object result) {
            testResult.set(result);
        }

        @Override
        public void onError(Throwable error) {
            throw new RuntimeException(error);
        }
    });

    ShadowLooper.idleMainLooper(5000);
    Robolectric.shadowOf(
        Reflection.field("handler")
            .ofType(Handler.class)
            .in(AndroidSchedulers.mainThread())
            .get().getLooper())
            .idle(5000);

    verify(apiMock, times(1)).requestA();
    verify(apiMock, times(1)).requestB();

    assertNotNull(testResult.get());
}
0
Martynas Jurkus