Оператор RxJava + Retrofit share () - PullRequest
       32

Оператор RxJava + Retrofit share ()

0 голосов
/ 19 сентября 2018

Особый сценарий 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()
}

Надеемся, что кто-то может дать объяснение этому.

1 Ответ

0 голосов
/ 08 октября 2018

Как обсуждалось в комментариях, проблема заключается в заметной инициализации.Вот более подробное объяснение.

Проблема заключается в следующем:

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.Я думаю, что вы также можете просто удалить ленивую инициализацию.Возможно, даже использовать внедрение зависимостей, чтобы не лениво инициализировать объект в тестах, но сохранять его лениво инициализированным в производственном приложении.

...