Search code examples
javaandroidkotlinandroid-viewmodelkoin

How to test my viewModel that is Koin injected?


I have a ViewModel. It calls a funtion in my data repo and gets back a list of the dog object.

class MainViewModel() : ViewModel() {
    private val dataRepo: DataRepo by inject(DataRepo::class.java) //dataRepo
    private var limit = 10
    private val _dogListLiveData = MutableLiveData<List<Dog>>()
    private var dogList = mutableListOf<Dog>()

    val dogListLiveData: MutableLiveData<List<Dog>>
        get() = _dogListLiveData

    fun searchByBreed(queryText: String) {
        dataRepo.searchByBreed(
            queryText,
            object : DataSource.OnResponseCallback<List<Dog>, String> {
                override fun onSuccess(obj: List<Dog>?) {
                    dogList = mutableListOf()
                    if(!obj.isNullOrEmpty()){
                    dogList.addAll(obj)
                    dogListLiveData.value = dogList.take(limit)
                    }

                }

                override fun onError(error: String) {
                    Log.i("Calling Network Service", error)
                }
            })

    }

    fun loadPaginateBreed() : Boolean{
        return if ((limit+10) < dogList.size) {
            limit += 10
            Log.i("Pagination new Limit", limit.toString())
            dogListLiveData.value = dogList.take(limit)
            false
        }else{
            limit += dogList.size%limit
            dogListLiveData.value = dogList.take(limit)
            true
        }
    }
}

I need to write a simple unit test for it. I've written this and tried many other iterations. But nothing seems to work.

package com.example.koinapplication.ui.main

import androidx.lifecycle.Observer
import com.example.koinapplication.custom.adpaters.GranularErrorCallAdapterFactory
import com.example.koinapplication.models.Dog
import com.example.koinapplication.models.Height
import com.example.koinapplication.models.Weight
import com.example.koinapplication.repo.*
import org.junit.After
import org.junit.Before
import org.junit.Test

import org.junit.Assert.*
import org.koin.android.ext.koin.androidContext
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.core.context.startKoin
import org.koin.core.context.stopKoin
import org.koin.dsl.module
import org.koin.java.KoinJavaComponent.inject
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
import java.util.*

class MainViewModelTest {


    private val mainViewModel: MainViewModel by inject(MainViewModel::class.java)

    lateinit var obserserData : Observer<List<Dog>>

    private val networkModule = module {
        factory { AuthInterceptor() }
        factory { provideOkHttpClient(get()) }
        factory { GranularErrorCallAdapterFactory<Any>() }
        single { providesNetworkClient(get(), get()) }
        single { DataRepo(get()) }
        single { NetworkRepo(get()) }
    }


    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)
        startKoin {
            modules(arrayListOf(networkModule))
        }
    }

    @After
    fun tearDown() {
        stopKoin()
    }

    @Test
    fun searchByBreed() {
        mainViewModel.dogListLiveData.observeForever { obserserData }
        mainViewModel.searchByBreed("dal")
        Mockito.verify(obserserData).onChanged(mainViewModel.dogListLiveData.value)
    }
}

Please help me write a simple test to test the data in my viewModel. Help will be hugely appreciated.


Solution

  • It is good practice keeping all Dependency Injection (DI) Frameworks out of your ViewModel. This makes your ViewModel independent and simplifies your unit tests.

    ViewModel

    So instead of injecting your dependency in your ViewModel you can pass them via constructor:

    class MainViewModel(
        private val dataRepo: dataRepo
    ) : ViewModel() {
        // ...
    }
    

    Koin Module

    In your Koin module you can define the dependencies and provide an instance of your ViewModel:

    module {
      single { DataRepo(get()) }
      factory { MainViewModel(dataRepo = get())}
    }
    

    In your Activity or Fragment you can then inject your ViewModel as usual.

    Unit Test

    In your MainViewModelTest you do not need any code for Koin:

    class MainViewModelTest {
        private val dataRepo: DataRepo = mockk() // I used Mockk for mocking, but you can use any other mocking framework
    
        private val mainViewModel = MainViewModel(dataRepo) // your class under test
    
        @Test
        fun yourTest() {
            // prepare
            every { dataRepo.searchByBreed(...)} returns ...
    
            mainViewModel.searchByBreed(queryText = "...")
    
            // do assertions
        }
    }
    

    Having this, simplifies writing unit tests a lot. And you could exchange Koin with any other DI Framework later without the need touching your ViewModel and the test(s).

    I hope this helps you a bit.