模拟改造在Android单元测试中的可观察<t>响应</t>

时间:2014-06-04 15:16:28

标签: android unit-testing mockito robolectric retrofit

我有一个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中的处理程序中抓取了looper并将其推进。结果相同。

更新#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());
}

3 个答案:

答案 0 :(得分:13)

我仍然围绕如何正确使用rxjava,但我会尝试修改你的代码,以便你只在最终的压缩Observable上观察On(mainThread),而不是在原始请求中执行响应的可观察性。然后我会验证这是否会影响你必须推进两个Loopers的事实。

为了简单地进行测试并消除对Looper空转的需要,我会将线程排除在等式之外,因为您在运行测试时不需要后台处理。您可以通过注入Scheduler而不是静态创建它们来实现。在运行生产代码时,您已经注入了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);
}

答案 1 :(得分:1)

您使用的是什么版本的rxjava?我知道有关ExecutorScheduler的0.18。*版本有一些变化。我在使用0.18.3时遇到了类似的问题,我不会得到onComplete消息,因为我的订阅会提前取消订阅。我向你提到这一点的唯一原因是0.19.0中的修复解决了我的问题。

不幸的是,我无法真正解释所修复内容的细节,此时我的理解已经超出了我的理解,但如果事实证明是相同的原因,也许有更多理解的人可以解释。这是我正在谈论的https://github.com/Netflix/RxJava/issues/1219的链接。

这不是一个很好的答案,但如果它可以帮助你更多的提醒。

答案 2 :(得分:0)

正如@ champ016所述,RxJava版本存在低于0.19.0的问题。

使用0.19.0时,以下方法有效。虽然仍然不太明白为什么我必须推进BOTH loopers。

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