Monday, 1 March 2021

Unit testing RxJava repeatWhen operator always stuck on the first iteration

I'm trying to unit test my viewmodel code, which does a server polling to return data as soon as its Status == ELIGIBLE

My problem is, it always assert when it's still loading (repeating), and not waiting for the onSuccess to be called to assert the correct status.

I've put some logs to track what's happening:

doOnSubscribe called
repeatWhen called
doOnNext called
takeUntil called
doOnNext called
takeUntil called

As you can see, repeatWhen and takeUntil are called twice (which is expected), but after that, no onSuccess called.

And eventually the test fails with this message Caused by: java.lang.AssertionError: expected:<SUCCESS> but was:<LOADING>

If I removed the failing line, the next assertion would fail too with message:

java.lang.AssertionError: 
Expected :ELIGIBLE
Actual   :null

Which mean the onSuccess method is not yet reached, and is still loading.

I also don't prefer using Schedulers.trampoline() .. it works, but it waits for 5 secs synchronously. I prefer to use TestScheduler.advanceByTime() instead.

Here's the client code:

fun startStatusPolling() {
    val pollingSingle = shiftPayService.obtainCardStatus()
            .repeatWhen {
                println("repeatWhen called")
                //POLLING_INTERVAL_SECONDS = 5
                it.delay(POLLING_INTERVAL_SECONDS, TimeUnit.SECONDS)
            }
            .takeUntil { item ->
                println("takeUntil called")
                item.cardStatus != Status.PENDING
            }.doOnNext {
                println("doOnNext called")
            }
            .lastElement()
            .toSingle()

    subscribe(pollingSingle, pollingStatusLiveData)
}

And my test class:

@RunWith(HomebaseRobolectricTestRunner::class)
@LooperMode(LooperMode.Mode.PAUSED)
class CardViewModelTest {

    lateinit var viewModel: CardViewModel
    var testScheduler = TestScheduler()

    @Before
    fun setup() {
        RxJavaPlugins.setComputationSchedulerHandler { testScheduler }
        RxJavaPlugins.setIoSchedulerHandler { testScheduler }
        RxAndroidPlugins.setInitMainThreadSchedulerHandler { testScheduler }

        val cardStatusPending: CardStatus = mockk(relaxed = true) {
            every { status } returns Status.PENDING
        }

        val cardStatusEligible: CardStatus = mockk(relaxed = true) {
            every { status } returns Status.ELIGIBLE
        }

        val cardService: CardService = spyk {
            every { obtainCardStatus() } returnsMany listOf(
                    Single.just(cardStatusPending),
                    Single.just(cardStatusEligible)
            )
        }

        viewModel = CardViewModel(cardService)
    }

    @Test
    fun testCardStatusPolling() {
        viewModel.startStatusPolling()
        shadowOf(Looper.getMainLooper()).idle()

        testScheduler.advanceTimeBy(5, TimeUnit.SECONDS)

        //after 5 sec delay, single is resubscibed, returning the second single cardStatusEligible
        assertEquals(Result.Status.SUCCESS, viewModel.pollingStatusLiveData.value?.status)
        assertEquals(EligiblityStatus.ELIGIBLE, viewModel.pollingStatusLiveData.value?.data?.eligibilityStatus)

    }
}


from Unit testing RxJava repeatWhen operator always stuck on the first iteration

No comments:

Post a Comment