Testing Rx Java interval

Viewed 3123

I have a repeated observable inside a ViewModel as below:

class MainViewModel @Inject constructor(private val ratesUseCase: RatesUseCase) : ViewModel() {

    private var disposable: Disposable? = null
    private val resultLiveData = MutableLiveData<Resource<Map<String, Double>>>()

    fun result() = resultLiveData
    fun getRates(base: String) {
        disposable = Observable
            .interval(1, TimeUnit.SECONDS)
            .flatMap { ratesUseCase.execute(base) }
            .doOnSubscribe { publishResult(Resource.loading(mapOf())) }
            .subscribe(
                {
                    publishResult(Resource.success(values))
                },
                {
                    publishResult(Resource.error(it.localizedMessage, mapOf()))
                }
            )
    }

}

I want to test getRates function. It always return LOADING status What is missing here?

Test case :

@Test
    fun executeSuccess() {
        val base = "CANARY"
        whenever(currencyRepository.getRates(base))
            .thenReturn(Observable.just(map))

        viewModel.result().observeForever(observer)
        viewModel.getRates(base)
        verify(observer).onChanged(Resource.success(map))
    }
2 Answers

Thats what happens if you don't pass Scheduler to Observable.interval.

@CheckReturnValue
@SchedulerSupport(SchedulerSupport.COMPUTATION)
public static Observable<Long> interval(long period, TimeUnit unit) {
    return interval(period, period, unit, Schedulers.computation());
}

It will use Schedulers.computation() as default Scheduler.

Your test thread will just finish while interval will not manage to emit anything.

You have to you use TestScheduler to test interval properly. See here: Unit testing Rxjava observables that have a delay

You can override computation scheduler or pass TestScheduler to Observable.interval in your ViewModel. Then you can TestScheduler.advanceTimeBy and control your interval emissions.

I did fix the tests here is how I did it. there are two observable here:

1- Observable with interval() of 1 second.

2- Observable of ratesUseCase.execute(base). as you can see below this how is it inside the MainViewModel

        Observable
        .interval(1, TimeUnit.SECONDS)
        .flatMap { ratesUseCase.execute(base) }
        .doOnSubscribe { publishResult(Resource.loading(mapOf())) }
        .subscribe(
            {
                publishResult(Resource.success(values))
            },
            {
                publishResult(Resource.error(it.localizedMessage, mapOf()))
            }
        )

To test getRates in MainViewModel

1- Inject Test schedulers for ratesUseCase.execute(base)

2- Use RxJavaPlugins.setComputationSchedulerHandler to change the default Scheduler as below

@Before
fun setUp() {
    val testScheduler = TestScheduler()
    RxJavaPlugins.setComputationSchedulerHandler { testScheduler }

    ratesUseCase = RatesUseCase(appSchedulers, currencyRepository)
    viewModel = MainViewModel(ratesUseCase)
}

3- Use testScheduler.advanceTimeTo to test interval. Please not the order here is important as below:

3.a : Mock repository

3.b : Observe ViewModel LiveData

3.c : Advance Time interval

3.d : Verify result

    val base = "BASE"
    whenever(currencyRepository.getRates(base))
        .thenReturn(Observable.just(map))

    viewModel.result().observeForever(observer)
    viewModel.getRates(base)

    testScheduler.advanceTimeTo(1, TimeUnit.SECONDS)
    verify(observer).onChanged(
        Resource.success(map)
    )

Full project and test cases can be found here

Related