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.
Related
Which thread the subsequently called functions are executed?
You may consider a function like the getNewsFeed() function inside the repository class given below -
#Singleton
class NewsFeedRepository #Inject constructor(
private val networkDataSource: NetworkDataSource,
private val diskDataSource: DiskDataSource
) {
#WorkerThread
suspend fun getNewsFeed(): NewsResponse {
return try {
val news = networkDataSource.getNewsFeed()
diskDataSource.updateCache(news)
NewsResponse(news = news)
} catch (ex: Exception) {
NewsResponse(
news = diskDataSource.getNews(),
errorMessage = ex.message
)
}
}
}
I called the getNewsFeed() function from ViewModel using kotlin coroutine as given below -
#HiltViewModel
class MainViewModel #Inject constructor(
private val repository: NewsFeedRepository
) : ViewModel() {
private val _newsResponse = MutableLiveData<NewsResponse>()
val newsResponse: LiveData<NewsResponse>
get() = _newsResponse
init {
viewModelScope.launch {
_newsResponse.value = repository.getNewsFeed()
}
}
}
I tried to use Dispatchers.IO as well but I couldn't update livedata value hence I had to use #WorkerThread.
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)
}
}
}
I get an error (MainViewModel has no zero argument constructor).
It seems to me that mistake is in misusing Hilt, but I can't find. There are similar questions on SA, but they don't fit my case.
I can't find where did I go wrong and will be gratifeul for any help.
Error:
java.lang.RuntimeException: Cannot create an instance of class mypackage.main.MainViewModel
/* bla bla bla */
Caused by: java.lang.InstantiationException: java.lang.Class<mypackage.main.MainViewModel> has no zero argument constructor
at java.lang.Class.newInstance(Native Method)
at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:219)
... 39 more
ViewModel begins like that:
#HiltViewModel
class MainViewModel #Inject constructor(
private val repo: MainRepository,
private val dispatchers: DispatcherProvider
) : ViewModel() {
// body
}
In MainActivity:
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val viewModel: MainViewModel by viewModels()
// etc
AppModule:
#Module
#InstallIn(SingletonComponent::class)
object AppModule {
#Singleton
#Provides
fun provideCurrencyApi() : CurrencyApi = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(CurrencyApi::class.java)
#Singleton
#Provides
fun provideMainRepository(api: CurrencyApi): MainRepository = DefaultMainRepository(api)
#Singleton
#Provides
fun provideDispatchers(): // blablabla
}
}
MainRepository:
interface MainRepository {
suspend fun getRates(base: String) : Resource<CurrencyResponse>
}
DefaultMainRepository
class DefaultMainRepository #Inject constructor(
private val api: CurrencyApi
) : MainRepository {
override suspend fun getRates(base: String): Resource<CurrencyResponse> {
return try {
val response = api.getRates(base)
val result = response.body()
if (response.isSuccessful && result != null) {
Resource.Success(result)
} else {
Resource.Error(response.message())
}
} catch (e: Exception) {
Resource.Error(e.message ?: "An error occurred")
}
}
}
I solved this problem by changing Dagger Hilt dependencies versions to earlier. I think there was mismatch in those versions. The rest of the code turned out to be correct, it seems..
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
)
}
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.