【发布时间】:2019-02-23 03:51:52
【问题描述】:
这里有特殊的 rx 场景:
改造 API:
interface MyApi {
@Headers("Content-Type: application/json")
@POST("something")
fun doSomething(@Body body: SomeRequestBody): Single<SomeResponse>
}
可以从多个地方调用此 API。所以,我想分享一下。我的存储库公开了这一点:
class Repository {
private val observable: Observable<SomeResponse> by lazy {
myApi.doSomething(SomeRequestBody())
.toObservable()
.share()
}
fun doSomething(): Completable {
observable.flatMapCompletable { Completable.complete() }
}
}
我正在使用以下内容进行测试:
// passes, as expected
@Test
fun `multiple api calls share`() {
given(myApi.doSomething(any())).willReturn(Single.just(RESPONSE).delay(2, SECONDS))
val test1 = repository.doSomething().test()
val test2 = repository.doSomething().test()
val test3 = repository.doSomething().test()
test1.await(3, SECONDS)
test2.await(3, SECONDS)
test3.await(3, SECONDS)
test1.assertNoErrors()
test2.assertNoErrors()
test3.assertNoErrors()
test1.assertComplete()
test2.assertComplete()
test3.assertComplete()
verify(myApi, times(1) /* for clarity */).doSomething(any())
}
// fails :(
@Test
fun `multiple api calls, one after the other`() {
given(myApi.doSomething(any())).willReturn(Single.just(RESPONSE).delay(2, SECONDS))
.willReturn(Single.just(OTHER_RESPONSE).delay(2, SECONDS))
val test1 = repository.doSomething().test()
test1.await(3, SECONDS)
test1.assertNoErrors()
test1.assertComplete()
// even tried explicitly disposing here
test1.dispose()
val test2 = repository.doSomething().test()
test2.await(3, SECONDS)
test2.assertNoErrors()
test2.assertComplete()
// fails here
verify(myApi, times(2)).doSomething(any())
}
我的理解是,如果所有订阅都已被处置,shared observable 将处置其来源。而当test2 调用doSomething() 时,会发生另一个API 调用。第二次测试未能反映这一点。
另外,如果我将 API 调用包装在 defer() 中,则两个测试都通过:
private val observable: Observable<SomeResponse> by lazy {
Single.defer {
myApi.doSomething(SomeRequestBody())
}.toObservable().share()
}
希望有人可以对此提供解释。
【问题讨论】:
-
我可能没有正确理解这一点,但这不是因为您正在懒惰地初始化存储库内部可观察对象吗?它被初始化一次并调用
doSomething方法,但是当repo 的实例保持不变时,它永远不会再次调用它。将其包装在defer中,使其成为订阅后执行的可观察对象的一部分,但defer本身仅被调用一次。当您订阅时,不会对defer进行新的调用,但它将再次运行源代码,执行doSomething行并且测试通过。这可以理解吗?去掉惰性部分还能用吗? -
@Fred 我想就是这样。我还注意到,即使没有
Signle.defer/fromCallable,针对MockWebServer的测试(而不是像上面那样模拟API)也可以工作。因此,Rx 调用适配器可能正在做类似的事情。如果不麻烦,您可以将其发布为答案吗?谢谢:) -
当然!很高兴,我稍后会在我的笔记本电脑上做这件事 :) 很高兴它成功了。
标签: android rx-java retrofit retrofit2 rx-java2