when I run this class, I'm always got test failed in method verify_on_success_is_called() with error,
Actually, there were zero interactions with this mock.
but if I run method only, test will passed.
#Mock
lateinit var mDummy: Dummy
private lateinit var mainViewModel: MainViewModel
#Mock
lateinit var main: MainViewModel.IMain
#Before
#Throws(Exception::class)
fun setup() {
MockitoAnnotations.initMocks(this)
MainViewModel.mIMain = main
RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }
mainViewModel = MainViewModel(mDummy)
}
#Test
fun verify_on_success_is_called() {
val mockList: ArrayList<Employee> = ArrayList()
mockList.add(Employee(1, "a", 20000.0, 22))
val list: List<Employee> = mockList
`when`(mDummy.getEmployees()).thenReturn(Observable.just(Response.success(list)))
mainViewModel.getEmployees()
Mockito.verify(main, times(1)).onSuccess()
}
#Test
fun verify_on_onError_is_called() {
MainViewModel.mIMain = main
`when`(mDummy.getEmployees()).thenReturn(Observable.error(Throwable()))
mainViewModel.getEmployees()
Mockito.verify(main, times(1)).onError()
}
this the viewModel class I want to test
class MainViewModel(private val mDummy: Dummy) : ViewModel() {
companion object {
lateinit var mIMain: IMain
}
interface IMain {
fun onSuccess()
fun onError()
}
fun getEmployees() {
mDummy.getEmployees()
.observeOn(SchedulerProvides.main())
.subscribeOn(SchedulerProvides.io())
.subscribe({ response ->
if (response.isSuccessful) {
mIMain.onSuccess()
} else {
mIMain.onError()
}
}, {
mIMain.onError()
})
}
and this my mainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
MainViewModel.mIMain = mIMainViewModelIniliazed()
}
private fun mIMainViewModelIniliazed() = object :MainViewModel.IMain{
override fun onSuccess() {
}
override fun onError() {
}
}
Please correct me if am wrong but i think your problem is because you're setting
MainViewModel.mIMain = main
before creating your viewmodel instance, shouldn't be as below?
mainViewModel = MainViewModel(mDummy)
mainViewModel.mIMain = main
Related
MainActivity
class MainActivity : AppCompatActivity() {
#Inject
lateinit var mainViewModelFactory: mainViewModelFactory
private lateinit var mainActivityBinding: ActivityMainBinding
private lateinit var mainViewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainActivityBinding = DataBindingUtil.setContentView(
this,
R.layout.activity_main
)
mainActivityBinding.rvmainRepos.adapter = mainAdapter
AndroidInjection.inject(this)
mainViewModel =
ViewModelProviders.of(
this#MainActivity,
mainViewModelFactory
)[mainViewModel::class.java]
mainActivityBinding.viewmodel = mainViewModel
mainActivityBinding.lifecycleOwner = this
mainViewModel.mainRepoReponse.observe(this, Observer<Response> {
repoList.clear()
it.success?.let { response ->
if (!response.isEmpty()) {
// mainViewModel.saveDataToDb(response)
// mainViewModel.createWorkerForClearingDb()
}
}
})
}
}
MainViewModelFactory
class MainViewModelFactory #Inject constructor(
val mainRepository: mainRepository
) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>) =
with(modelClass) {
when {
isAssignableFrom(mainViewModel::class.java) -> mainViewModel(
mainRepository = mainRepository
)
else -> throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
}
} as T
}
MainViewModel
class MainViewModel(
val mainRepository: mainRepository
) : ViewModel() {
private val compositeDisposable = CompositeDisposable()
val mainRepoReponse = MutableLiveData<Response>()
val loadingProgress: MutableLiveData<Boolean> = MutableLiveData()
val _loadingProgress: LiveData<Boolean> = loadingProgress
val loadingFailed: MutableLiveData<Boolean> = MutableLiveData()
val _loadingFailed: LiveData<Boolean> = loadingFailed
var isConnected: Boolean = false
fun fetchmainRepos() {
if (isConnected) {
loadingProgress.value = true
compositeDisposable.add(
mainRepository.getmainRepos().subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ response ->
run {
saveDataToDb(response)
)
}
},
{ error ->
processResponse(Response(AppConstants.Status.SUCCESS, null, error))
}
)
)
} else {
fetchFromLocal()
}
}
private fun saveDataToDb(response: List<mainRepo>) {
mainRepository.insertmainUsers(response)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(object : DisposableCompletableObserver() {
override fun onComplete() {
Log.d("Status", "Save Success")
}
override fun onError(e: Throwable) {
Log.d("Status", "error ${e.localizedMessage}")
}
})
}
}
MainRepository
interface MainRepository {
fun getmainRepos(): Single<List<mainRepo>>
fun getAllLocalRecords(): Single<List<mainRepo>>
fun insertmainUsers(repoList: List<mainRepo>): Completable
}
MainRepositoryImpl
class mainRepositoryImpl #Inject constructor(
val apiService: GitHubApi,
val mainDao: AppDao
) : MainRepository {
override fun getAllLocalRecords(): Single<List<mainRepo>> = mainDao.getAllRepos()
override fun insertmainUsers(repoList: List<mainRepo>) :Completable{
return mainDao.insertAllRepos(repoList)
}
override fun getmainRepos(): Single<List<mainRepo>> {
return apiService.getmainGits()
}
}
I'm quite confused with the implementation of MVVM with LiveData and Rxjava, in my MainViewModel I am calling the interface method and implementing it inside ViewModel, also on the response I'm saving the response to db. However, that is a private method, which won't be testable in unit testing in a proper way (because it's private). What is the best practice to call other methods on the completion of one method or i have to implement all the methods inside the implementation class which uses the interface.
Your ViewModel should not care how you are getting the data if you are trying to follow the clean architecture pattern. The logic for fetching the data from local or remote sources should be in the repository in the worst case where you can also save the response. In that case, since you have a contact for the methods, you can easily test them. Ideally, you could break it down even more - adding Usecases/Interactors.
I am using Junit & Mockito 4 for unit testing of viewModel.
ViewModel class
class MainViewModel(app: Application, private val githubRepo: GithubRepository) :
BaseViewModel(app) {
private val _trendingLiveData by lazy { MutableLiveData<Event<DataState<List<TrendingResponse>>>>() }
val trendingLiveData: LiveData<Event<DataState<List<TrendingResponse>>>> by lazy { _trendingLiveData }
var loadingState = MutableLiveData<Boolean>()
fun getTrendingData(language: String?, since: String?) {
launch {
loadingState.postValue(true)
when (val result = githubRepo.getTrendingListAsync(language, since).awaitAndGet()) {
is Result.Success -> {
loadingState.postValue(false)
result.body?.let {
Event(DataState.Success(it))
}.run(_trendingLiveData::postValue)
}
is Result.Failure -> {
loadingState.postValue(false)
}
}
}
}
}
Api EndPoinit
interface GithubRepository {
fun getTrendingListAsync(
language: String?,
since: String?
): Deferred<Response<List<TrendingResponse>>>
}
ViewModel Test class
#RunWith(JUnit4::class)
class MainViewModelTest {
#Rule
#JvmField
val instantTaskExecutorRule = InstantTaskExecutorRule()
#Mock
lateinit var repo: GithubRepository
#Mock
lateinit var githubApi: GithubApi
#Mock
lateinit var application: TrendingApp
lateinit var viewModel: MainViewModel
#Mock
lateinit var dataObserver: Observer<Event<DataState<List<TrendingResponse>>>>
#Mock
lateinit var loadingObserver: Observer<Boolean>
private val threadContext = newSingleThreadContext("UI thread")
private val trendingList : List<TrendingResponse> = listOf()
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
Dispatchers.setMain(threadContext)
viewModel = MainViewModel(application, repo)
}
#Test
fun test_TrendingRepo_whenSuccess() {
//Assemble
Mockito.`when`(githubApi.getTrendingListAsync("java", "daily"))
.thenAnswer{ return#thenAnswer trendingList.toDeferred() }
//Act
viewModel.trendingLiveData.observeForever(dataObserver)
viewModel.loadingState.observeForever(loadingObserver)
viewModel.getTrendingData("java", "daily")
Thread.sleep(1000)
//Verify
verify(loadingObserver).onChanged(true)
//verify(dataObserver).onChanged(trendingList)
verify(loadingObserver).onChanged(false)
}
#After
fun tearDown() {
Dispatchers.resetMain()
threadContext.close()
}
}
Problem is that my livedata is wrapped around Event<DataState<List<TrendingResponse>>, due to which I am not able to get what should be dataObserver and how should I verify that dataObserver in the test class.
Event os open class that is to handle event like SingleLiveData
DataState is sealed class that contain SUCCESS & FAILED data class
I have written test case livedata is like LiveData<List<Response> or something like that.
You need to wrap the List<TrendingResponse> → Event(DataState.Success(List<TrendingResponse>)) which you are returning using mockito - trendingList.toDeferred().
#Test
fun test_TrendingRepo_whenSuccess() {
//Assemble
Mockito.`when`(githubApi.getTrendingListAsync("java", "daily"))
.thenAnswer{ return#thenAnswer trendingList.toDeferred() }
//Act
viewModel.trendingLiveData.observeForever(dataObserver)
viewModel.loadingState.observeForever(loadingObserver)
viewModel.getTrendingData("java", "daily")
Thread.sleep(1000)
//Verify
verify(loadingObserver).onChanged(true)
//wrap the trendingList inside Event(DataState(YourList))
verify(dataObserver).onChanged(Event(DataState.Success(trendingList)))
verify(loadingObserver).onChanged(false)
}
I am trying to mock a response from my usecases, this usecase works with coroutines.
fun getData() {
view?.showLoading()
getProductsUseCase.execute(this::onSuccessApi, this::onErrorApi)
}
My useCase is injected on presenter.
GetProductsUseCase has this code:
class GetProductsUseCase (private var productsRepository: ProductsRepository) : UseCase<MutableMap<String, Product>>() {
override suspend fun executeUseCase(): MutableMap<String, Product> {
val products =productsRepository.getProductsFromApi()
return products
}
}
My BaseUseCase
abstract class UseCase<T> {
abstract suspend fun executeUseCase(): Any
fun execute(
onSuccess: (T) -> Unit,
genericError: () -> Unit) {
GlobalScope.launch {
val result = async {
try {
executeUseCase()
} catch (e: Exception) {
GenericError()
}
}
GlobalScope.launch(Dispatchers.Main) {
when {
result.await() is GenericError -> genericError()
else -> onSuccess(result.await() as T)
}
}
}
}
}
This useCase call my repository:
override suspend fun getProductsFromApi(): MutableMap<String, Product> {
val productsResponse = safeApiCall(
call = {apiService.getProductsList()},
error = "Error fetching products"
)
productsResponse?.let {
return productsMapper.fromResponseToDomain(it)!!
}
return mutableMapOf()
}
Y try to mock my response but test always fails.
#RunWith(MockitoJUnitRunner::class)
class HomePresenterTest {
lateinit var presenter: HomePresenter
#Mock
lateinit var view: HomeView
#Mock
lateinit var getProductsUseCase: GetProductsUseCase
#Mock
lateinit var updateProductsUseCase: UpdateProductsUseCase
private lateinit var products: MutableMap<String, Product>
private val testDispatcher = TestCoroutineDispatcher()
private val testScope = TestCoroutineScope(testDispatcher)
#Mock
lateinit var productsRepository:ProductsRepositoryImpl
#Before
fun setUp() {
Dispatchers.setMain(testDispatcher)
products = ProductsMotherObject.createEmptyModel()
presenter = HomePresenter(view, getProductsUseCase, updateProductsUseCase, products)
}
#After
fun after() {
Dispatchers.resetMain()
testScope.cleanupTestCoroutines()
}
//...
#Test
fun a() = testScope.runBlockingTest {
setTasksNotAvailable(productsRepository)
presenter.getDataFromApi()
verify(view).setUpRecyclerView(products.values.toMutableList())
}
private suspend fun setTasksNotAvailable(dataSource: ProductsRepository) {
`when`(dataSource.getProductsFromApi()).thenReturn((mutableMapOf()))
}
}
I don't know what is happening. The log says:
"Wanted but not invoked:
view.setUpRecyclerView([]);
-> at com.myProject.HomePresenterTest$a$1.invokeSuspend(HomePresenterTest.kt:165)
However, there was exactly 1 interaction with this mock:
view.showLoading();"
The problem is with how you create your GetProductsUseCase.
You're not creating it with the mocked version of your ProductsRepository, yet you're mocking the ProductsRepository calls.
Try to create the GetProductsUseCase manually and not using a #Mock
// no #Mock
lateinit var getProductsUseCase: GetProductsUseCase
#Before
fun setUp() {
// ...
// after your mocks are initialized...
getProductsUseCase = GetProductsUseCase(productsRepository) //<- this uses mocked ProductsRepository
}
I am trying to write a simple test for my MainPresenter(SchedulerProvider) class. It should check if the showEventFragment(String) and showPromptFragment(String) shows up after successful API response.
I create MainPresenter instance in MainActivity like this:
private val presenter: MainContract.Presenter = MainPresenter(AppSchedulerProvider())
Presenter class:
class MainPresenter(private val scheduler: SchedulerProvider) : MainContract.Presenter{
private val subscriptions = CompositeDisposable()
private val api: RxApiServiceInterface = RxApiServiceInterface.create()
private lateinit var view: MainContract.View
override fun subscribe() {
}
override fun unsubscribe() {
subscriptions.clear()
}
override fun loadProfileData() {
view.showLoadingView()
val subscribe = api.getProfileDataRequest()
.subscribeOn(scheduler.io())
.observeOn(scheduler.ui())
.subscribe({profileList : List<Profile> ->
view.showEventFragment(profileList[0].profile_id)
view.showPromptFragment(profileList[0].profile_id)
},{ error ->
view.showErrorMessage(error.message.toString())
view.showRetryView()
})
subscriptions.add(subscribe)
}
override fun attach(view: MainContract.View) {
this.view = view
}
}
Test class:
class MainPresenterTest {
#Mock
private lateinit var view: MainContract.View
private var api: RxApiServiceInterface = RxApiServiceInterface.create()
private lateinit var mainPresenter: MainPresenter
private lateinit var testScheduler: TestScheduler
private lateinit var testSchedulerProvider: TestSchedulerProvider
#Before
fun setup() {
MockitoAnnotations.initMocks(this)
testScheduler = TestScheduler()
testSchedulerProvider = TestSchedulerProvider(testScheduler)
mainPresenter = MainPresenter(testSchedulerProvider)
mainPresenter.attach(view)
}
#Test
fun getProfileSuccess() {
val address = Address("SillyStreet", "4", "80333", "NY")
val contact = Contact("i.am#dummy.com", "000 000 000 00")
val mockedProfile = Profile(
"freemium_profile",
"Dum",
"my",
"male",
"Sil ly",
true,
"First Class",
"1989-01-01",
address,
contact,
listOf("freemium", "signup_complete")
)
doReturn(Single.just(listOf(mockedProfile)))
.`when`(api)
.getProfileDataRequest()
mainPresenter.loadProfileData()
testSchedulerProvider.testScheduler.triggerActions()
verify(view).showLoadingView()
verify(view).showEventFragment(mockedProfile.profile_id)
verify(view).showPromptFragment(mockedProfile.profile_id)
}
}
RxApiServiceInterface interface:
interface RxApiServiceInterface {
#GET("user/customer/profiles")
fun getProfileDataRequest() : Single<List<Profile>>
companion object {
private val restClient by lazy {
RestClient.createRetrofit(API_URL)
}
fun create(): RxApiServiceInterface = restClient.create(RxApiServiceInterface::class.java)
}
}
TestSchedulerProvider class:
class TestSchedulerProvider constructor(val testScheduler: TestScheduler) : SchedulerProvider {
override fun ui(): Scheduler = testScheduler
override fun computation(): Scheduler = testScheduler
override fun io(): Scheduler = testScheduler
}
I am using these test libs:
testImplementation 'org.mockito:mockito-core:2.22.0'
testImplementation 'org.mockito:mockito-inline:2.22.0'
testImplementation 'org.jetbrains.kotlin:kotlin-test-junit:1.2.71'
androidTestImplementation 'org.mockito:mockito-android:2.7.22'
What the hell am I doing wrong, that I still get the "Wanted but not invoked error"?
The logs also says:
However, there was exactly 1 interaction with this mock:
view.showLoadingView();
but I am not suprised about that as this method is outside API query.
I'm trying to learn Mockito on Android and using the mockito-kotlin library by nhaarman. I'm getting a strange Mockito error that reads:
"org.mockito.exceptions.misusing.CannotStubVoidMethodWithReturnValue:
'execute' is a void method and it cannot be stubbed with a return value"
The strange thing is that I'm not stubbing this class. Here's the unit test that's having the problem.
#RunWith(JUnit4::class)
class PoiListViewModelTest {
#get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
//Mocks
#Mock lateinit var getPois: GetPois
#Mock lateinit var mapper: PoiMapper
#Mock lateinit var observer: Observer<List<Poi_Presentation>>
// Class being tested
lateinit var poiListViewModel: PoiListViewModel
#Before
fun setup() {
MockitoAnnotations.initMocks(this)
poiListViewModel = PoiListViewModel(getPois,mapper)
}
private fun stubMapper(poi_Domain: Poi_Domain, poiPresentation: Poi_Presentation) {
`when`(mapper.mapToPresentation(poi_Domain)).thenReturn(poiPresentation)
}
#Test
fun fetchAllPoisExecutesUseCaseOnStart(){
verify(getPois, times(1)).execute(any(), eq(null))
}
#Test
fun poiSubscriberOnNextMapsCorrectly(){
val poiDomains = TestDataFactory.makePoiDomainList(2)
val poiPresentations = TestDataFactory.makePoiPresentationList(2)
stubMapper(poiDomains[0],poiPresentations[0])
poiListViewModel.getAllPois().observeForever(observer)
poiListViewModel.PoiSubscriber().onNext(poiDomains)
assertEquals(poiListViewModel.getAllPois().value,poiPresentations)
}
}
And here is my class being tested:
open class PoiListViewModel #Inject constructor(
private val getPois: GetPois,
private val mapper: PoiMapper
) : ViewModel() {
private val poiData: MutableLiveData<List<Poi_Presentation>> = MutableLiveData()
init {
fetchAllPois()
}
fun getAllPois(): LiveData<List<Poi_Presentation>> {
return poiData
}
fun fetchAllPois() {
getPois.execute(PoiSubscriber())
}
override fun onCleared() {
getPois.dispose()
super.onCleared()
}
inner class PoiSubscriber: DisposableObserver<List<Poi_Domain>>(){
override fun onComplete() {
Log.i("poiSubscriber: ", "completed")
}
override fun onNext(data: List<Poi_Domain>) {
poiData.postValue(data.map {
mapper.mapToPresentation(it)
})
}
override fun onError(e: Throwable) {
Log.e("Obervable error: ", e.localizedMessage )
}
}
}
It's a short unit test, and you can see that I'm not trying to stub execute, so why is Mockito giving me this error? the execute method does fire when my test class get's created, but I used a mock object (getPois), so the method should do nothing by default correct? Strangely the error occurs when I attempt to stub another class PoiMapper. Here's that class:
open class PoiMapper #Inject constructor() {
fun mapToPresentation(domainModel: Poi_Domain): Poi_Presentation {
return Poi_Presentation(domainModel.id,domainModel.name,domainModel.description,
domainModel.img_url,domainModel.latitude,domainModel.longitude,domainModel.imgFocalpointX,
domainModel.imgFocalpointY,domainModel.collection,domainModel.collectionPosition,
domainModel.release,domainModel.stampText)
}
}
This method does have a return type, so it should not be a part of the problem. I'm hoping someone can explain to me what's going on as I'm stumped. Does it have something to do with the fact that I'm calling this method in a lambda?