RxJava + Retrofit share()运算符

时间:2018-09-19 04:15:50

标签: android rx-java retrofit retrofit2 rx-java2

此处特殊的接收场景:

改造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()
}

希望有人可以对此提供解释。

1 个答案:

答案 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中来解决此问题。我认为您也可以简单地删除延迟的初始化。也许,甚至可以使用依赖注入在测试中不延迟地初始化对象,而将其在生产应用程序中延迟地初始化。