RX is a great spell but at times certain things can make you wish you had more hair on your head if you miss out on some fundamentals. Here’s one of my ‘GOTCHA’ moments.
After spending about a day or so trying to figure out why a simple test kept failing, a man was frustrated.
@RunWith(MockitoJUnitRunner::class)
class HairPullingTest {
private var testScheduler = TestScheduler()
@Test
fun testVodoo() {
val subject:PublishSubject<Int> = PublishSubject.create()
val testObserver = subject
.subscribeOn(testScheduler)
.observeOn(testScheduler)
.test()
subject.onNext(1)
testScheduler.triggerActions()
testObserver.assertValueCount(1)
}
}
Fast forward he also realised something weird
So something unusual is up, so he heads over to StackOverflow and with some help from Dávid Karnok adds an assert statement.
So a man goes about adding logs to his test.
@RunWith(MockitoJUnitRunner::class)
class HairPullingTest {
private var testScheduler = TestScheduler()
@Test
fun testVodoo() {
val subject:PublishSubject<Int> = PublishSubject.create()
val testObserver = subject
.subscribeOn(testScheduler)
.observeOn(testScheduler)
.doOnSubscribe {
print("I've subscribed")
}.test()
print("Emitting Item")
subject.onNext(1)
testScheduler.triggerActions()
testObserver.assertValueCount(1)
}
}
and…
So what’s really happening here?
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. 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.
The solution is to invoke triggerActions immediately after subscribing and then again after emission.
@RunWith(MockitoJUnitRunner::class)
class DemoViewModelTest {
private var testScheduler = TestScheduler()
@Test
fun testVodoo() {
val subject: PublishSubject<Int> = PublishSubject.create()
val testObserver = subject
.subscribeOn(testScheduler)
.observeOn(testScheduler)
.test()
testScheduler.triggerActions()
subject.onNext(1)
testScheduler.triggerActions()
testObserver.assertValueCount(1)
}
}
#Conclusion
Always add an extra triggerAction() with your TestScheduler right after subscribing your subjects to ensure the subsequent emission happen as intended.