此处特殊的接收场景:
改造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
可以观察到的将被丢弃。当test2
调用doSomething()
时,将发生另一个API调用。第二次测试未能反映出这一点。
另一件事,如果我将API调用包装在defer()
中,则两个测试均通过:
private val observable: Observable<SomeResponse> by lazy {
Single.defer {
myApi.doSomething(SomeRequestBody())
}.toObservable().share()
}
希望有人可以对此提供解释。
答案 0 :(得分:1)
如评论中所述,问题是可观察到的初始化。这是更详细的说明。
问题出在这里:
private val observable: Observable<SomeResponse> by lazy {
myApi.doSomething(SomeRequestBody())
.toObservable()
.share()
}
变量observable
的初始化是延迟的,这意味着只要我们使用存储库的相同实例,它就只会初始化一次。
因此,在测试中,您有一个存储库实例和多个测试。这意味着,对于整个测试类,lazy
块中的代码只运行一次。意思是,myApi.doSomething(any())
运行一次。当您尝试验证多个交互时,这会导致失败。
将其包装在defer
中时起作用的原因是defer
创建了一个可观察的对象,该变量将在订阅者每次订阅时执行(在您的情况下,由于share
运算符,但思路相同)。像以前一样,defer
会延迟执行,并且在测试期间再也不会调用。也就是说,如果可以验证对defer
的调用,那么结果将相同。但是,现在每次可观察对象应运行,它将调用myApi.doSomething(any())
,并且测试将通过。
您已经知道,可以通过将调用包装在defer
中来解决此问题。我认为您也可以简单地删除延迟的初始化。也许,甚至可以使用依赖注入在测试中不延迟地初始化对象,而将其在生产应用程序中延迟地初始化。