Kotlin RxJava Retrofit MVVM Unit Test Cases

This article is about writing unit test cases for a viewmodel implemented using MVVM design pattern with Retrofit and Mockito Unit Test cases

RetroViewModelFactory:

This class injects retrofit instance and sends API service instance as argument to ViewModel (RetroRXViewModel)

RetroViewModelFactory:

class RetroCoroutineViewModelFactory : ViewModelProvider.Factory {
@Inject
lateinit var retrofit: Retrofit
lateinit var apiService: APIService

@ExperimentalStdlibApi
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
DaggerRetroRxComponent.create().inject(this)
apiService = retrofit.create(APIService::class.java)

if (modelClass.isAssignableFrom(RetroCoroutineViewModel::class.java)) {
return RetroCoroutineViewModel(Dispatchers.Main,apiService) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}


}

RetroViewModel:

This ViewModel makes API call and fetches data from it.

Used RXJava to do API call on Scheduler.io and fetch results on AndroidSchedulers.mainThread()

@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun fetchRetroInfo(){
compositeDisposable.add(apiService.makeRequest()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : DisposableSingleObserver<List<RetroRxModel>>(){
override fun onSuccess(t: List<RetroRxModel>) {

mutableLiveData.value = t
loading.value = false
}
override fun onError(e: Throwable) {
e.printStackTrace()
postLoadError.value = e.message
loading.value = false
// onError(e.localizedMessage)
}
})
)
}

APIService Interface:

This interface has methods to interact with API service

fun makeRequest() — -> Fetches data from API

interface APIService {
// https://jsonplaceholder.typicode.com/posts
@GET("/posts")
fun makeRequest(): Single<List<RetroRxModel>>


@GET("/posts")
suspend fun fetchPosts(): Response<List<RetroRxModel>>


@GET("/posts")
suspend fun fetchUserPosts(): Response<List<RetroRxModel>>
}

Mockito Unit Test case to Test the ViewModel(RetroRXViewModelToTest):

This class unit tests viewmodel

The below variables to be declared:

// A JUnit Test Rule that swaps the background executor used by
// the Architecture Components with a different one which executes each task synchronously.
// You can use this rule for your host side tests that use Architecture Components.
@Rule
@JvmField
var rule = InstantTaskExecutorRule()

APIService to be mocked to bypass API call.

@Mock
lateinit var apiService: APIService

Create Dummy Data to be returned when API call is made with mocked data

@Test
fun fetchRetroInfoTest_success(){
var retroRxModel = RetroRxModel("tile","body","1")
var retroRXModelList = ArrayList<RetroRxModel>()
retroRXModelList.add(retroRxModel)
single = Single.just(retroRXModelList)

if(apiService!=null){
Mockito.`when`(apiService.makeRequest()).thenReturn(single)
}


retroRXViewModel.fetchRetroInfo()
Assert.assertEquals(1,retroRXViewModel.mutableLiveData.value?.size)
Assert.assertEquals(loading,retroRXViewModel.loading.value)
}

As RXJava is used ,in order to return immediately during unittest when making API call on ioThread , we need create a new rule to return immediately during unittest to make Rxjava run synchronously .

Create a new rule for RXJava to run synchronously

class RxImmediateSchedulerRule  : TestRule {

private val immediate = object : Scheduler() {
override fun createWorker(): Worker {
return ExecutorScheduler.ExecutorWorker(Executor { it.run() },false)
}

override fun scheduleDirect(run: Runnable, delay: Long, unit: TimeUnit): Disposable {
// this prevents StackOverflowErrors when scheduling with a delay
return super.scheduleDirect(run, 0, unit)
}


}


override fun apply(base: Statement?, description: Description?): Statement {
return object : Statement() {
@Throws(Throwable::class)
override fun evaluate() {
RxJavaPlugins.setInitIoSchedulerHandler { scheduler -> immediate }
RxJavaPlugins.setInitComputationSchedulerHandler { scheduler -> immediate }
RxJavaPlugins.setInitNewThreadSchedulerHandler { scheduler -> immediate }
RxJavaPlugins.setInitSingleSchedulerHandler { scheduler -> immediate }
RxAndroidPlugins.setInitMainThreadSchedulerHandler { scheduler -> immediate }

try {
base?.evaluate()
} finally {
RxJavaPlugins.reset()
RxAndroidPlugins.reset()
}
}
}
}
}

Complete TestCase Code:

@RunWith(MockitoJUnitRunner::class)
class RetroRXViewModelToTest {

// A JUnit Test Rule that swaps the background executor used by
// the Architecture Components with a different one which executes each task synchronously.
// You can use this rule for your host side tests that use Architecture Components.
@Rule
@JvmField
var rule = InstantTaskExecutorRule()

// Test rule for making the RxJava to run synchronously in unit test
companion object {
@ClassRule
@JvmField
val schedulers = RxImmediateSchedulerRule()
}

@Mock
lateinit var apiService: APIService

lateinit var retroRXViewModel: RetroRXViewModel

private lateinit var single: Single<List<RetroRxModel>>

private var loading: Boolean = false

@Before
fun setUp() {
// initialize the ViewModed with a mocked github api
retroRXViewModel = RetroRXViewModel(apiService)
}

@Test
fun fetchRetroInfoTest_success(){
var retroRxModel = RetroRxModel("tile","body","1")
var retroRXModelList = ArrayList<RetroRxModel>()
retroRXModelList.add(retroRxModel)
single = Single.just(retroRXModelList)

if(apiService!=null){
Mockito.`when`(apiService.makeRequest()).thenReturn(single)
}


retroRXViewModel.fetchRetroInfo()
Assert.assertEquals(1,retroRXViewModel.mutableLiveData.value?.size)
Assert.assertEquals(loading,retroRXViewModel.loading.value)
}


@Test
fun fetchRetroInfoTest_Failure_Scenario(){
single = Single.error(Throwable())
Mockito.`when`(apiService.makeRequest()).thenReturn(single)
retroRXViewModel.fetchRetroInfo()
Assert.assertEquals(loading,retroRXViewModel.loading.value)
//Assert.assertNull(object : Any??)
}
}

Link to Code:

RetroViewModelFactory.kt:

RetroRXViewModel.kt:

APIService.kt

RxImmediateSchedulerRule.kt

RetroRXViewModelToTest.kt

Dependencies:

testImplementation "org.mockito:mockito-core:3.3.3"
testImplementation 'androidx.arch.core:core-testing:2.1.0'
def rxJava_version = "2.2.19"
def rxAndroid_version = "2.1.1"
implementation "io.reactivex.rxjava2:rxandroid:$rxAndroid_version"
implementation "io.reactivex.rxjava2:rxjava:$rxJava_version"


def retrofit_version = "2.6.1"
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:retrofit-converters:$retrofit_version"
implementation "com.squareup.retrofit2:retrofit-adapters:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofit_version"