3

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.

Kush Singh
  • 157
  • 3
  • 11

1 Answers1

2

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.

ChristianB
  • 2,452
  • 2
  • 10
  • 22