UnitTest coroutines Kotlin usecase MVP - android

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
}

Related

Android unit testing using Mockito : can't get the right behaviour for mocks

I am testing my Repository class using Mockito, specifically getProducts() functionality:
class Repository private constructor(private val retrofitService: ApiService) {
companion object {
#Volatile
private var INSTANCE: Repository? = null
fun getInstance(retrofitService: ApiService): Repository {
synchronized(this) {
var instance = INSTANCE
if (instance == null) {
instance = Repository(retrofitService)
}
INSTANCE = instance
return instance
}
}
}
suspend fun getProducts(): ProductsResponse = withContext(IO) {
retrofitService.getProducts()
}
}
This is my test class:
#ExperimentalCoroutinesApi
#RunWith(MockitoJUnitRunner::class)
class RepositoryTest {
// Class under test
private lateinit var repository: Repository
// Executes each task synchronously using Architecture Components.
#get:Rule
val instantExecutorRule = InstantTaskExecutorRule()
// Set the main coroutines dispatcher for unit testing.
#ExperimentalCoroutinesApi
#get:Rule
var mainCoroutineRule = MainCoroutineRule()
#Mock
private lateinit var retrofitService: ApiService
#Before
fun createRepository() {
MockitoAnnotations.initMocks(this)
repository = Repository.getInstance(retrofitService)
}
#Test
fun test() = runBlocking {
// GIVEN
Mockito.`when`(retrofitService.getProducts()).thenReturn(fakeProductsResponse)
// WHEN
val productResponse: ProductsResponse = repository.getProducts()
println("HERE = ${retrofitService.getProducts()}")
// THEN
println("HERE: $productResponse")
MatcherAssert.assertThat(productResponse, `is`(fakeProductsResponse))
}
}
And my ApiService:
interface ApiService {
#GET("https://www...")
suspend fun getProducts(): ProductsResponse
}
When I call repository.getProducts(), it returns null despite the fact, that I explicitly set retrofitService.getProducts() to return fakeProductsResponse, which is being called inside repository's getProducts() method. It should return fakeProductsResponse, but it returns null.
Am I doing wrong mocking or what the problem can be? Thanks...
EDIT: this is my MainCoroutineRule, if you need it
#ExperimentalCoroutinesApi
class MainCoroutineRule(val dispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()):
TestWatcher(),
TestCoroutineScope by TestCoroutineScope(dispatcher) {
override fun starting(description: Description?) {
super.starting(description)
Dispatchers.setMain(dispatcher)
}
override fun finished(description: Description?) {
super.finished(description)
cleanupTestCoroutines()
Dispatchers.resetMain()
}
}
It might not be a complete solution to your problem, but what I see is that your MainCoroutineRule overrides the mainDispatcher Dispatchers.setMain(dispatcher).
But in
suspend fun getProducts(): ProductsResponse = withContext(IO)
you are explicitely setting an IO Dispatcher.
I recommend always setting the dispatcher from a property you pass via constructor:
class Repository private constructor(
private val retrofitService: ApiService,
private val dispatcher: CoroutineDispatcher) {
companion object {
fun getInstance(retrofitService: ApiService,
dispatcher: CoroutineDispatcher = Dispatchers.IO): Repository {
// ommit code for simplicity
instance = Repository(retrofitService, dispatcher)
// ...
}
}
}
suspend fun getProducts(): ProductsResponse = withContext(dispatcher) {
retrofitService.getProducts()
}
}
Having it a default parameter you do not need to pass it in your regular code, but you can exchange it within your unit test:
class RepositoryTest {
private lateinit var repository: Repository
#get:Rule
var mainCoroutineRule = MainCoroutineRule()
#Mock
private lateinit var retrofitService: ApiService
#Before
fun createRepository() {
MockitoAnnotations.initMocks(this)
repository = Repository.getInstance(retrofitService, mainCoroutineRule.dispatcher)
}
}
For my own unit tests I am using the blocking function of TestCoroutineDispatcher from the CoroutineRule like:
#Test
fun aTest() = mainCoroutineRule.dispatcher.runBlockingTest {
val acutal = classUnderTest.callToSuspendFunction()
// do assertions
}
I hope this will help you a bit.

Android Unit Test ViewModel Wanted but not invoked

I'm new on unit testing. I'm trying to do unit testing on my view model class but my test fail with error:
Wanted but not invoked:
toggleMovieFavorite.invoke(
Movie(id=1, title=Title, overview=Overview, releaseDate=01/01/2025, posterPath=, backdropPath=, originalLanguage=ES, originalTitle=Title, popularity=5.0, voteAverage=7.0, favorite=false)
);
-> at xyz.jonthn.usescases.ToggleMovieFavorite.invoke(ToggleMovieFavorite.kt:7)
Actually, there were zero interactions with this mock.
This is my test file
#RunWith(MockitoJUnitRunner::class)
class DetailViewModelTest {
#get:Rule
val rule = InstantTaskExecutorRule()
#Mock
lateinit var findMovieById: FindMovieById
#Mock
lateinit var toggleMovieFavorite: ToggleMovieFavorite
#Mock
lateinit var observer: Observer<Movie>
private lateinit var vm: DetailViewModel
#ExperimentalCoroutinesApi
#Before
fun setUp() {
Dispatchers.setMain(Dispatchers.Unconfined)
vm = DetailViewModel(1, findMovieById, toggleMovieFavorite, Dispatchers.Unconfined)
}
#ExperimentalCoroutinesApi
#After
fun tearDown() {
Dispatchers.resetMain()
}
#Test
fun `when favorite clicked, the toggleMovieFavorite use case is invoked`() {
runBlocking {
val movie = mockedMovie.copy(id = 1)
whenever(findMovieById.invoke(1)).thenReturn(movie)
whenever(toggleMovieFavorite.invoke(movie)).thenReturn(movie.copy(favorite = !movie.favorite))
vm.movie.observeForever(observer)
vm.onFavoriteClicked()
verify(toggleMovieFavorite).invoke(movie)
}
}
val mockedMovie = Movie(
0,
"Title",
"Overview",
"01/01/2025",
"",
"",
"ES",
"Title",
5.0,
7.0,
false)
}
This is my DetailViewModel:
class DetailViewModel(
private val movieId: Int, private val findMovieById: FindMovieById,
private val toggleMovieFavorite: ToggleMovieFavorite,
uiDispatcher: CoroutineDispatcher) : ScopedViewModel(uiDispatcher) {
private val _movie = MutableLiveData<Movie>()
val movie: LiveData<Movie> get() = _movie
init {
launch {
_movie.value = findMovieById.invoke(movieId)
}
}
fun onFavoriteClicked() {
launch {
movie.value?.let {
_movie.value = toggleMovieFavorite.invoke(it)
}
}
}
}
And my use case ToggleMovieFavorite:
class ToggleMovieFavorite(private val moviesRepository: MoviesRepository) {
suspend fun invoke(movie: Movie): Movie = with(movie) {
copy(favorite = !favorite).also { moviesRepository.update(it) }
}
}
Thank you so much for your help guys!!!
i thougt mockito does not invoke your init method on viewmodel, you should put your declaration of vm on each #test instead of #Before since method findMovieById called at init, right before the function is mocked.

Unit test always fail when class is run

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

Write unit Testcase for ViewModel in kotlin

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)
}

RxJava how to test debounce?

I am trying to test RxJava2's "debounce" operator in the Android.
I used the "debounce" operator for the search feature.
In the View(Activity), searching is started.
etSearch.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(editable: Editable) {
mPresenter.search(editable.toString())
}
...
})
And the Presenter is here:
class MyPresenter(
private val view: MyContract.View,
private val apiService: ApiServie = ApiServiceImpl(),
private val searchSubject: PublishSubject<String> = PublishSubject.create(),
var debounceScheduler: Scheduler = Schedulers.computation()
) : MyContract.Presenter {
init {
setupSearch()
}
override fun search(keyword: String) {
if (searchDisposable.size() == 0) {
setupSearch()
}
searchSubject.onNext(keyword)
}
private fun setupSearch() {
searchSubject.debounce(1000, TimeUnit.MILLISECONDS, debounceScheduler)
.distinctUntilChanged()
.switchMap { keyword ->
apiService.search(keyword)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { response ->
response.data?.let { data ->
view.searchResult(data)
}
}
}
}
Actually, it works fine when I do the integration test.
But what I want to do is testing the "search" function of the MyPresenter class.
To do this, I read an article
But it doesn't work...
My test code is here:
class MyTest {
#Mock
private lateinit var mockView: MyContract.View
#Mock
private lateinit var mockApiService: ApiService
private lateinit var mMyPresenter: MyPresenter
private lateinit var inOrder: InOrder
private val mTestScheduler: TestScheduler = TestScheduler()
private val ZERO = 0
private fun setUpScheduler() {
val immediate = object : Scheduler() {
override fun createWorker() = ExecutorScheduler.ExecutorWorker(Runnable::run)
}
RxJavaPlugins.setInitIoSchedulerHandler { immediate }
RxAndroidPlugins.setInitMainThreadSchedulerHandler { immediate }
}
#Before
fun setup() {
MockitoAnnotations.initMocks(this)
setUpScheduler()
mMyPresenter = MyPresenter(mockView, mockApiService)
inOrder = inOrder(mockView)
}
#Test
fun searchBillsTest() {
`when`(mockApiService.search("America"))
.thenReturn(Observable.just(mockResult))
mMyPresenter.debounceScheduler = mTestScheduler
mMyPresenter.search("America")
mTestScheduler.advanceTimeBy(1000, TimeUnit.MILLISECONDS)
verify(mockView).searchResult(mockResult)
}
}
The last verify is not called...
I don't know why...
I added "doOnNext" to print log to find the cause in "searchSubject", but "doOnNext" is not called...
"doOnSubscribe" is called...
Please do you know why?
In order to as quick as pass your test code just change few things.
Use TestScheduler
Divide ui and io scheduler.
Use Scheduler.triggerActions before trigger events for subject.
#RunWith(MockitoJUnitRunner::class)
class DebounceTest {
private lateinit var searchSubject: PublishSubject<String>
#Mock lateinit var apiService: ApiService
#Mock lateinit var presenter: Presenter
private lateinit var disposable: Disposable
private lateinit var ioTestScheduler: TestScheduler
private lateinit var uiTestScheduler: TestScheduler
#Before fun setUp() {
searchSubject = PublishSubject.create<String>()
ioTestScheduler = TestScheduler()
uiTestScheduler = TestScheduler()
setupSearch(uiTestScheduler, ioTestScheduler)
// important https://stackoverflow.com/a/53543257/1355048
ioTestScheduler.triggerActions()
}
#After fun tearDown() {
disposable.dispose()
}
#Test fun searchBillsTest() {
`when`(apiService.search("America")).thenReturn(Observable.just("MOCK RESULT"))
searchSubject.onNext("America")
ioTestScheduler.advanceTimeBy(1, TimeUnit.SECONDS)
uiTestScheduler.triggerActions()
verify(presenter).doSomething("MOCK RESULT")
}
private fun setupSearch(uiScheduler: Scheduler, ioScheduler: Scheduler) {
disposable = searchSubject.debounce(1, TimeUnit.SECONDS, ioScheduler)
.distinctUntilChanged()
.switchMap { apiService.search(it) }
.subscribeOn(ioScheduler)
.observeOn(uiScheduler)
.subscribe { response ->
presenter.doSomething(response)
}
}
interface ApiService {
fun search(query: String): Observable<String>
}
interface Presenter {
fun doSomething(result: String)
}
}

Categories

Resources