Race conditions in unit tests with RxJava when using TestScheduler

I recently had a very strange bug when it comes to using a BehaviorSubject inside the object that I am testing.

The case

We have a Presenter. This presenter receives events through an Observable called uiEvents. It listens for them and reacts differently based on the type of the event that it receives. So we have something like this

class MyPresenter (
  private val uiEvents: Observable<UiEvent>,
  private val uiScheduler: Scheduler,
  private val ioScheduler: Scheduler
) {

  val actions: Observable<PresenterAction> = PublishSubject.create()

  fun createSubscriptions() {
    uiEvents.ofType(ButtonClickEvent::class.java)
      .switchMapSingle { callTheApi() }
      .subscribeOn(ioScheduler)
      .observeOn(uiScheduler)
      .subscribe({ actions.onNext(OpenThatScreenAction()) })
  }

  private fun callTheApi() = Single.just(1,2,3)
}

class MyPresenterTest {
  private val uiEvents = BehaviorSubject.create<UiEvent>()
  private val testScheduler = TestScheduler()
  private val underTest = MyPresenter(
    uiEvents = uiEvents,
    uiScheduler = testScheduler,
    ioScheduler = testScheduler
  )

  @Test
  fun returnsOpenThatScreenAction() {
    val testObserver = underTest.actions.test()
    underTest.createSubscriptions()

    uiEvents.onNext(ButtonClickEvent())

    testObserver.assertValue { it is OpenThatScreenAction }
  }
}

Simple as that. And you want to test that when you submit an event the expected thing happens. But basically this assertion never passes. You can add doOnSubscribe and see that it gets called but when you try to assert that the value count is 1, it never passes.

The answer

There is this small article that gives the answer.

Turns out the actual subscription(subscribeActual() method in PublishSubject) which adds the subscriber to Subject’s list of subscribers doesn’t happen until triggerActions is invoked on the TestScheduler.

So you need to call the testScheduler.triggerActions() in order to get the subscribe method called. This results in a race condition where even though the subscriber seems subscribed, the actual subscription and emission happen concurrently. The emission is therefore ignored due to absence of any subscribers.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s