Testing a minimal livedata viewmodel using Mockito - android

I have the following viewmodel:
class ListViewModel(application: Application) : AndroidViewModel(application) {
private val appDatabase: AppDatabase by lazy {
AppDatabase.getInstance(application)
}
private val items: LiveData<List<Item>> by lazy {
appDatabase.itemDao().getAllItems()
}
fun getAllItems(): LiveData<List<Item>> {
return items
}
fun deleteItemById(id: Long) {
launch { async { appDatabase.itemDao().deleteItemById(id) } }
}
}
I have a test suite already in place at the DAO level, and because this viewmodel is simple an abstraction layer over my DAOs, I'm struggling to figure out how to create meaningful tests for this viewmodel.
I've tried mocking the DAOs using Mockito but can't seem to implement them properly. My latest attempt looks like this:
class ListViewModelTest {
lateinit var listViewModel: ListViewModel
#Mock
lateinit var context: Application
#Mock
lateinit var appDatabase: AppDatabase
#Mock
lateinit var items: LiveData<List<Item>>
#Before
fun setup() {
MockitoAnnotations.initMocks(this)
setupMocks()
listViewModel = ListViewModel(context)
}
fun setupMocks() {
`when`<Context>(context.applicationContext).thenReturn(context)
`when`(appDatabase.itemDao().getAllItems()).thenReturn(items)
}
#Test
fun getAllItems() {
listViewModel.getAllItems()
verify(appDatabase).itemDao().getAllItems()
}
#Test
fun deleteFilesystem() {
listViewModel.deleteItemById(0)
verify(appDatabase).itemDao().deleteItemById(0)
}
}
I'm pretty new to testing in general, so any general advice is also appreciated.

Related

How can I test live data object in Android MVVM

Hi I am trying to test livedata which is in my viewmodel, I have written to test to verify that the repository call has been made
how can I check if the live data object in this case, and what additional test cases I should be writing for this code in viewModel
Thanks
R
MainActivityViewModel
class MainActivityViewModel #Inject constructor(
private val dataRepository: DataRepository
): ViewModel() {
val _charactersLiveData = MutableLiveData<Result<ArrayList<Character>>>()
val charactersLiveData: LiveData<Result<ArrayList<Character>>> = _charactersLiveData
fun fetchCharacters() {
viewModelScope.launch {
_charactersLiveData.value = dataRepository.getCharacters()
}
}
}
MainActivityViewModelTest
#RunWith(MockitoJUnitRunner::class)
class MainActivityViewModelTest {
#get:Rule
val coroutineRule = MainCoroutineRule()
#Mock
private lateinit var dataRepository: DataRepository
#InjectMocks
private lateinit var mainActivityViewModel: MainActivityViewModel
#Test
fun fetchCharacters() {
runBlockingTest {
mainActivityViewModel.fetchCharacters()
verify(dataRepository).getCharacters()
}
}
}
You can reference this article: https://medium.com/androiddevelopers/unit-testing-livedata-and-other-common-observability-problems-bb477262eb04
The main idea here is:
Mock dataRepository
When dataRepository calls getCharacters(), it should return the testing value. E.g. listOf('a')
Execute mainActivityViewModel.fetchCharacters()
Expect the value of your live data
assertEquals(mainActivityViewModel.charactersLiveData.getOrAwaitValue(), listOf('a'))
Try this if any issues then let me know. I have try to do according to your class and data but if you need then can changes.
#RunWith(MockitoJUnitRunner::class)
class MainActivityViewModelTest {
#get:Rule
val coroutineRule = MainCoroutineRule()
#Mock
private lateinit var dataRepository: DataRepository
#InjectMocks
private lateinit var mainActivityViewModel: MainActivityViewModel
#Mock
private lateinit var dataObserver: Observer<Result<List<Character>>>
#Test
fun fetchCharacters() {
runBlockingTest {
Mockito.`when`(datarepository).getcharacter()
.thenreturn(Result.success(listOf(Character (
Name=A,
Type=hero))))
mainActivityViewModel.fetchCharacters()
mainActivityViewModel.charactersLiveData.observeForever(dataObserver)
Mockito.verify(dataRepository).getCharacters()
Mockito.verify(dataObserver).onChanged(Result.success(listOf(Character (
Name=A,
Type=hero))))
mainActivityViewModel.charactersLiveData.removeObserver(dataObserver)
}
}
}

Mockito cannot mock/spy because : - final class

I am a simple function getCreditReport in viewmodel in which I am trying to make an API call repository layer
I want to test getCreditReport in viewmodel to check that thge repository is called but I get the following error
org.mockito.exceptions.base.MockitoException:
Cannot mock/spy class com.example.clearscore.data.DataRepository
Mockito cannot mock/spy because :
- final class
What I have tried
MainActivityViewModel
class MainActivityViewModel #Inject constructor(
//private val retroServiceInterface: RetroServiceInterface,
private val dataRepository: DataRepository
) : ViewModel() {
var creditReportLiveData: MutableLiveData<CreditReport>
init {
creditReportLiveData = MutableLiveData()
}
fun getCreditReportObserver(): MutableLiveData<CreditReport> {
return creditReportLiveData
}
fun getCreditReport() {
viewModelScope.launch(Dispatchers.IO) {
try {
val response = dataRepository.getCreditReport()
creditReportLiveData.postValue(response)
Log.d("data", response.toString())
} catch (e: IOException) {
Log.d("data", e.toString())
}
}
}
}
DataRepository
class DataRepository #Inject constructor(
private val retroServiceInterface: RetroServiceInterface
) {
suspend fun getCreditReport(): CreditReport {
return retroServiceInterface.getDataFromApi()
}
}
Unit test
#RunWith(MockitoJUnitRunner::class)
class MainActivityViewModelTest {
#Mock
private lateinit var dataRepository: DataRepository
#Mock
private lateinit var mainActivityViewModel: MainActivityViewModel
#Test
fun getCreditReport() {
runBlocking {
mainActivityViewModel.getCreditReport()
verify(dataRepository).getCreditReport()
}
}
}
Dependency Injection component - in case this is helpful
#Singleton
#Component(modules = [RetroModule::class])
interface RetroComponent {
fun inject(mainActivityViewModel: MainActivityViewModel)
fun getMainactivityViewModel(): MainActivityViewModel
}
Please suggest what I am doing wrong
Thanks
R
Please try adding this dependency
testImplementation "org.mockito:mockito-inline:3.11.2"
[Adding to help future folks who stumble upon this question]
In my case, the class didn't need to be final so I just removed final from the class declaration.

What is the best way to test a function in my viewModel?

I started to implement automated tests recently and I have a little doubt of how to test my ViewModel functions. My project follows some clean architecture concepts, I'm using lib kotlin for mockito (https://github.com/nhaarman/mockito-kotlin) and I would like to do the following test: When call makeLoginUser get success then set loginUserLiveData .
I have studied some concepts and I know the tests I should do, but I am still in doubt as to how to do them, which objects should I mock for a given test and which should be instantiated. I needed a small example to address me.
Here is my test class and some classes of the structure of my project.
TestClass
#RunWith(JUnit4::class)
class MainViewModelTest {
#get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
#Mock
private lateinit var myRepository: MyRepository
private lateinit var loginUserUseCase: LoginUserUseCase
private lateinit var checkUserAuthenticatedUsecase: CheckUserAuthenticatedUsecase
private lateinit var logoutUserUseCase: LogoutUserUseCase
private lateinit var mainViewModel: MainViewModel
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
}
#Test
fun `When call makeLoginUser get success then set loginUserLiveData`() {
// prepare
myRepository = mock { // should I mock or instantiate?
on { execLoginUser(ArgumentMatchers.anyString(), ArgumentMatchers.anyString()) } doReturn Flowable.just(true)
}
loginUserUseCase = mock { // should I mock or instantiate?
on { execute(ArgumentMatchers.anyMap()) } doReturn Flowable.just(true)
}
mainViewModel = MainViewModel(loginUserUseCase, checkUserAuthenticatedUsecase, logoutUserUseCase)
val observer = mock<Observer<ResultState<Boolean>>> { mock }
// Execute
mainViewModel.makeLoginUser("test#gmail.com", "123456")
// Check
// ?
}
}
ViewModel
// components 'LoginUserUseCase', 'CheckUserAuthenticatedUsecase' and 'LogoutUserUseCase' injected via koin
class MainViewModel(
private val loginUserUseCase: LoginUserUseCase,
private val checkUserAuthenticatedUsecase: CheckUserAuthenticatedUsecase,
private val logoutUserUsecase: LogoutUserUseCase
): BaseViewModel() {
val loginUserLiveData = MutableLiveData<ResultState<Boolean>>()
val userAuthenticatedLiveData = MutableLiveData<ResultState<Boolean>>()
val logoutUserLiveData = MutableLiveData<ResultState<Boolean>>()
fun makeLoginUser(email: String, password: String) {
loginUserLiveData.postValue(ResultState.Loading())
loginUserUseCase.execute(mapOf(EMAIL to email, PASSWORD to password))
.subscribe({
loginUserLiveData.postValue(ResultState.Success(it))
}, {
loginUserLiveData.postValue(ResultState.Error(it))
}).addTo(disposables)
}
...
}
UseCase Domain Class
// components 'ThreadExecutor' and 'PostExecutionThread' injected via koin
abstract class FlowableUseCase<T, in Params> constructor(
private val threadExecutor: ThreadExecutor,
private val postExecutionThread: PostExecutionThread) {
protected abstract fun buildUseCaseObservable(params: Params? = null): Flowable<T>
open fun execute(params: Params? = null): Flowable<T> {
return this.buildUseCaseObservable(params)
.subscribeOn(Schedulers.from(threadExecutor))
.observeOn(postExecutionThread.scheduler)
}
}
Abstraction UseCases
// components 'MyRepository', 'ThreadExecutor' and 'PostExecutionThread' injected via koin
// 'MyRepository' abstracts remote and local data repository
class LoginUserUseCase(
private val myRepository: MyRepository,
threadExecutor: ThreadExecutor,
postExecutionThread: PostExecutionThread
): FlowableUseCase<Boolean, Map<String, Any>?>(threadExecutor, postExecutionThread) {
override fun buildUseCaseObservable(params: Map<String, Any>?) = myRepository
.execLoginUser(
params?.get(EMAIL) as String,
params[PASSWORD] as String
)
}

How to unit test LiveData Transformations

I clearly don't understand how to unit test business logic inside Transformation. In my specific case I need to test Transformations.map, but I guess Transformations.switchmap would be the same.
The following is just an example of my scenario, and what I'd like to achieve.
MyViewModel.kt
class MyViewModel: ViewModel() {
private val _sampleLiveDataIwannaTest : MutableLiveData<Int> = MutableLiveData()
val sampleLiveDataIWannaTest: Livedata<Int> = _sampleLiveDataIWannaTest
// ...
val liveDataImNotInterestedIn = Transformations.map(myRepository.streamingData){
streaming->
_sampleLiveDataIwannaTest.postValue(streaming.firstElementValue +streaming.lastElementValue)
streaming
}
// ...
}
With:
val liveDataImNotInteresedIn : LiveData<Foo>
myRepository.streamingData : LiveData<Foo>
myRepository.streamingData is a data source that wakes up the Transformations.map which, in turn, starts the business logic I'm interested in (the value posted in _sampleLiveDataIwannaTest). In this particular test, I don't care about anything else.
MyViewModelTest.kt
class MyViewModelTest {
#get:Rule val rule = InstantTaskExecutorRule()
#RelaxedMockK
lateinit var myRepository : MyRepository
#OverrideMockKs
lateinit var sut: MyViewModel
#Before
fun setUp() {
MockKAnnotations.init(this, relaxUnitFun = true)
}
#Test
fun Transformations_Test(){
sut.liveDataImNotInterestedIn.observeForever{}
// 1)I really don't know how to mock the livedata that returns from
// myRepository.streamingData . Something like this is correct?
// every{myRepository.streamingData}.returns{< LiveData of type Int > }
// 2) I wish to write this kind of test:
//
// assertEquals(5, sampleLiveDataIWannaTest.value)
}
I'm using MockK instead of Mockito.
The unit test code will look like this:
class MyViewModelTest {
#get:Rule
val rule = InstantTaskExecutorRule()
#RelaxedMockK
lateinit var myRepository : MyRepository
#RelaxedMockK
lateinit var mockedSampleLiveDataIWannaTest : Observer<Int>
#OverrideMockKs
lateinit var sut: MyViewModel
#Before
fun setUp() {
MockKAnnotations.init(this, relaxUnitFun = true)
}
#Test
fun Transformations_Test(){
val expected = (*YOUR EXPECTED DATA HERE FROM REPOSITORY*)
every { myRepository.streamingData() } answers { expected }
sut.sampleLiveDataIWannaTest.observeForever(mockedSampleLiveDataIWannaTest)
verify { myRepository.streamingData() }
verify() { mockedSampleLiveDataIWannaTest.onChanged(Int) }
confirmVerified(myRepository, mockedSampleLiveDataIWannaTest)
}
if your repository is using coroutines then change every to coEvery and verify to coVerify
to learn more about MockK: https://mockk.io/

Mockito does'nt mock repository

i'm try to test my ViewModel with mockito.
This is my TestClass:
#RunWith(JUnit4::class)
class RatesViewModelTest {
#Rule #JvmField
open val instantExecutorRule = InstantTaskExecutorRule()
#Mock
var observer: Observer<Pair<ArrayList<CurrencyExchangerModel>,Boolean>>? = null
#Mock
private lateinit var repository: RatesRepository
private var currencyList = ArrayList<CurrencyModel>()
private lateinit var viewModel : RatesViewModel
#Before
fun setUp(){
MockitoAnnotations.initMocks(this)
currencyList.add(CurrencyModel("BASE"))
viewModel = RatesViewModel(repository!!)
viewModel.getCurrencyExchangerObservableList().observeForever(observer!!)
}
#Test
fun testNull(){
Mockito.`when`(repository.getFlowableRates()).thenReturn( Flowable.just(currencyList) )
assertTrue(viewModel.getCurrencyExchangerObservableList().hasObservers())
}
}
When this method is invoked:
Mockito.`when`(repository.getFlowableRates()).thenReturn( Flowable.just(currencyList) )
I got this error:
kotlin.UninitializedPropertyAccessException: lateinit property db has
not been initialized
Here the repository:
open class RatesRepository(context:Context) : BaseRepository(context){
#Inject
lateinit var ratesAPI: RatesAPI
#Inject
lateinit var db: Database
/**
* load the updated chatList from API
*/
fun loadCurrencyRatesFromAPI(): Single<ArrayList<CurrencyModel>> {
val supportedCurrency = context.resources.getStringArray(R.array.currencies)
return ratesAPI.getLatestRates(EUR_CURRENCY_ID).map { RatesConverter.getRatesListFromDTO(it,supportedCurrency) }
}
/**
* save rates on DB
*/
fun saveCurrencyRatesOnDB(list:ArrayList<CurrencyModel>): Completable {
return db.currencyRatesDAO().insertAll(list)
}
/**
* get flawable rates from DB
*/
fun getFlowableRates(): Flowable<List<CurrencyModel>> {
return db.currencyRatesDAO().getAll()
}
companion object {
const val EUR_CURRENCY_ID = "EUR" //BASE
}
}
What i'm doing wrong ?
Thx !
When you define behaviour of a mock and use the when(...).then(...) notation of mockito,
the method itself is invoked (by mockito, normally not relevant for your test).
In your case that is a problem because db is not initialized.
To avoid this issues use the doReturn(...).when(...) syntax in these cases,
which does not cause the method invocation.
Mockito.doReturn(Flowable.just(currencyList)).when(repository).getFlowableRates();
(You might need to adjust this java syntax to make it kotlin compatible)

Categories

Resources