Android Testing

From bibbleWiki
Jump to navigation Jump to search

Introduction

This, currently, is a minimum example of testing

Environment

Had a ton of problems trying to get this to work. Using the default for a project did not work. The test below using Retrofit and RxJava3.

    implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
    implementation 'io.reactivex.rxjava3:rxjava:3.0.13'

    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'

    testImplementation 'junit:junit:4.13.2'
    testImplementation 'org.mockito:mockito-inline:4.3.1'

Code to test

So this is the example I was testing. Specifically the RxJava part.

    private fun fetchCountries() {
        loading.value = true
        disposable.add(
            countriesService.getCountries()
                // Create new Thread
                .subscribeOn(Schedulers.newThread())
                // Provide Thread to received Response
                .observeOn(AndroidSchedulers.mainThread())
                // What to do with it
                .subscribeWith(object: DisposableSingleObserver<List<Country>>() {

                    // RxJava Success
                    override fun onSuccess(value: List<Country>?) {
                        Timber.d("All Good")
                        countries.value = value
                        countryLoadError.value = false
                        loading.value = false
                    }
                    // RxJava error
                    override fun onError(e: Throwable) {
                        Timber.d("All bad")
                        countryLoadError.value = true
                        loading.value = false
                    }
                })
        )
    }

The CountriesService is injected with Dagger2 and uses Retrofit to call an API call.

class CountriesService {
    @Inject
    lateinit var api: CountriesApi

    init {
        DaggerApiComponent.create().inject(this)
    }

    fun getCountries(): Single<List<Country>> = api.getCountries()
}

Unit Test

This uses the RxPlugins to mock the async functions

...

class ListViewModelTest {
    @get:Rule
    var rule = InstantTaskExecutorRule()

    @Mock
    lateinit var countriesService: CountriesService

    @InjectMocks
    var listViewModel = ListViewModel()

    private var testSingle: Single<List<Country>>? = null

    @Before
    fun setup() {
        MockitoAnnotations.openMocks(this)
    }

    @Test
    fun getCountriesSuccess() {
        val country = Country(countryName = "countryName", "capital", "url")
        val countriesList = arrayListOf(country, country)

        testSingle = Single.just(countriesList)

        `when`(countriesService.getCountries()).thenReturn(testSingle)

        listViewModel.refresh()

        assertEquals(2, listViewModel.countries.value?.size)
        assertEquals(false, listViewModel.countryLoadError.value)
        assertEquals(false, listViewModel.loading.value)
    }

    @Test
    fun getCountriesError() {

        testSingle = Single.error(Throwable( ))

        `when`(countriesService.getCountries()).thenReturn(testSingle)

        listViewModel.refresh()

        assertEquals(true, listViewModel.countryLoadError.value)
        assertEquals(false, listViewModel.loading.value)
    }

    @Before
    fun setUpClass() {
        RxJavaPlugins.reset()
        RxAndroidPlugins.reset()
        val immediate = object : Scheduler() {
            override fun scheduleDirect(run: Runnable, delay: Long, unit: TimeUnit): Disposable {

                return super.scheduleDirect(run, 0, unit)
            }

            override fun createWorker(): Worker {
                return ExecutorScheduler.ExecutorWorker({ it.run() }, true, true)
            }
        }
        RxJavaPlugins.setInitIoSchedulerHandler { immediate }
        RxJavaPlugins.setInitComputationSchedulerHandler { immediate }
        RxJavaPlugins.setInitNewThreadSchedulerHandler { immediate }
        RxJavaPlugins.setInitSingleSchedulerHandler { immediate }
        RxAndroidPlugins.setInitMainThreadSchedulerHandler { immediate }

        RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
        RxJavaPlugins.setComputationSchedulerHandler { Schedulers.trampoline() }
        RxJavaPlugins.setNewThreadSchedulerHandler { Schedulers.trampoline() }
        RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }
    }

    @After
    fun tearDownClass() {
        RxJavaPlugins.reset()
        RxAndroidPlugins.reset()
    }
}