我有一个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());
}
答案 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());
}