Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing RxJava with a Completable.timer

Android Studio 3.5.2
RxJava2
Kotlin 1.3.50

I have the following class that acts like a timer.

class DelayTimerImp(private val scheduler: IScheduler)
    : DelayTimer {

    override fun createTimeout(delay: Long, timeUnites: TimeUnit): Completable {
        return Completable.timer(delay, TimeUnit.SECONDS, scheduler.main())
    }
}

And I am using it in a method like this:

private fun startTimeoutRequest(delay: Long) {
    compositeSubscription.add(delayTimer.createTimeout(delay, TimeUnit.SECONDS)
            .subscribeOn(schedulers.io())
            .subscribe(::timeoutHasExceeded, ::timeoutError))
}

And in my test class and it uses the TestScheduler to return the io thread. I am mocking the delayTimer. Even though I thenReturn(Completable.complete()) the methods in the timeOutHasExceeded never go into them. I am thinking that if Completable has completed in go into that method.

@Test
fun `should reload and show loading state`() {
    // Arrange
    whenever(delayTimer.createTimeout(10, TimeUnit.SECONDS))
            .thenReturn(Completable.complete())

    // Act
    vipInformationPresenterImp.tryAgainTapped()

    // Assert methods in the timeOutHasExceeded
}

Also If I change the following to this by changing the delay to 1:

whenever(delayTimer.createTimeout(1, TimeUnit.SECONDS))
                .thenReturn(Completable.complete())

I will get a crash on the following line:

.subscribeOn(schedulers.io())

What is the best way to unit test this?

Many thanks in advance

like image 486
ant2009 Avatar asked Nov 11 '19 15:11

ant2009


1 Answers

According to this tutorial you should override these with TestScheduler:

class TestSchedulerProvider(private val scheduler: TestScheduler) : BaseSchedulerProvider {
    override fun computation() = scheduler
    override fun ui() = scheduler
    override fun io() = scheduler
}

Then later the tutorial makes use of TestScheduler as follows:

@Test
fun delayTestExample() {
    //given
    val presenter = DemoPresenter(testSchedulerProvider, view, service)
    given(service.getSomeRemoteData()).willReturn(Single.just(5))
    val delayInMillis = 1000L

    //when
    presenter.getSomeDataWithDelay(delayInMillis)

    //then
    then(view).should(never()).showData(anyInt())
    // HERE:
    testScheduler.advanceTimeBy(delayInMillis, TimeUnit.MILLISECONDS) 
    then(view).should().showData(5)
}

The use of advanceTimeBy is described under comment //HERE: in the code. Could the same approach be used so that instead of your:

whenever(delayTimer.createTimeout(1, TimeUnit.SECONDS))
            .thenReturn(Completable.complete())

you would call just for example:

testScheduler.advanceTimeBy(1500, TimeUnit.MILLISECONDS)

in the line after you create the timeout and that 1500 I selected so that the 1 second really gets ticked in the timer, before the advanceTimeBy continues. It should go well with every value > 1000 though, I just want to be sure enough ;)

like image 68
mico Avatar answered Oct 06 '22 16:10

mico