Подтвердить что ты не робот

Mock Retrofit Наблюдаемый <T> ответ в тестах Android Unit

У меня есть интерфейс 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()); все работает просто отлично - mock 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());
}
4b9b3361

Ответ 1

Я все еще обдумываю, как правильно использовать rxjava, но я бы попытался изменить ваш код, чтобы вы только наблюдали за окном (mainThread) на последнем zipped Observable вместо того, чтобы делать это как на исходном запросе ответа Observable, Затем я проверил, влияет ли это на то, что вам нужно продвигать оба лупера или нет.

Чтобы просто ваши тесты и удалить необходимость в холостом ходу Looper, я бы взял поток из уравнения, так как вам не нужна фоновая обработка при выполнении тестов. Вы можете это сделать, если ваши планировщики вводятся вместо их статического копирования. Когда вы запускаете свой производственный код, вы должны ввести 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);
}

Ответ 2

какую версию rxjava вы используете? Я знаю, что произошли некоторые изменения в версии 0.18. * Относительно ExecutorScheduler. У меня была аналогичная проблема с вами при использовании 0.18.3, где я не получил бы сообщение onComplete, потому что моя подписка будет отписана раньше времени. Единственная причина, по которой я упоминаю вам это, заключается в том, что исправление в 0.19.0 исправило мою проблему.

К сожалению, я не могу объяснить подробные сведения о том, что было исправлено, но это не мое понимание на данный момент, но если это окажется той же причиной, может быть, кто-то с большим пониманием может объяснить. Здесь ссылка о том, что я говорю о https://github.com/Netflix/RxJava/issues/1219.

Это не большая часть ответа, но больше хедз-ап, если он может вам помочь.

Ответ 3

Как указано в @champ016, были проблемы с версиями RxJava, которые ниже 0.19.0.

При использовании 0.19.0 работает следующий подход. Хотя все еще не совсем понятно, почему я должен продвигать BOTH петлителей.

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