How to test ViewModel + Flow with API call from init{} - android

I have ViewModel which exposes flow to fragment. I am calling API from ViewModel's init which emits different states. I am not able to write unit test to check all the emitted states.
My ViewModel
class FooViewModel constructor( fooProvider : FooProvider){
private val _uiState = MutableSharedFlow<UiState>(replay = 1)
// Used in fragment to collect ui states.
val uiState = _uiState.asSharedFlow()
init{
_uiState.emit(FetchingFoo)
viewModelScope.runCatching {
// Fetch shareable link from server [users.sharedInvites.list].
fooProvider.fetchFoo().await()
}.fold(
onSuccess = {
_uiState.emit(FoundFoo)
},
onFailure = {
_uiState.emit(EmptyFoo)
}
)
}
sealed class UiState {
object FetchingFoo : UiState()
object FoundFoo : UiState()
object EmptyFoo : UiState()
}
}
Now I want to test this ViewModel to check if all the states are being emitted.
My Test: Note I am using turbine library.
class FooViewModelTest{
#Mock
private lateinit var fooProvider : FooProvider
#Test
fun testFooFetch() = runTest {
whenever(fooProvider.fetchFoo()).thenReturn(// Expected API response)
val fooViewModel = FooViewModel(fooProvider)
// Here lies the problem. as we create fooViewModel object API is called.
// before reaching test block.
fooViewModel.uiState.test{
// This condition fails as fooViewModel.uiState is now at FoundFoo.
assertEquals(FetchingFoo, awaitItem())
assertEquals(FoundFoo, awaitItem())
}
}
}
How to delay init till inside on .test{} block.
Tried creating ViewModel object by Lazy{} but not working.

It is not very pragmatic to "delay" emissions for sake of testing, this may produce flakey tests.
This is more of a coding issue - the right question should be "Does this logic belong in the class initialisation. The fact that it is more difficult to test should give you hints that it is less than ideal.
A better solution would be to use a StateFlow which is lazily initialised something like (some code assumed for sake of testing) :
class FooViewModel constructor(private val fooProvider: FooProvider) : ViewModel() {
val uiState: StateFlow<UiState> by lazy {
flow<UiState> {
emit(FoundFoo(fooProvider.fetchFoo()))
}.catch { emit(EmptyFoo) }
.flowOn(Dispatchers.IO)
.stateIn(
scope = viewModelScope,
started = WhileSubscribed(5_000),
initialValue = FetchingFoo)
}
sealed class UiState {
object FetchingFoo : UiState()
data class FoundFoo(val list: List<Any>) : UiState()
object EmptyFoo : UiState()
}
}
fun interface FooProvider {
suspend fun fetchFoo(): List<Any>
}
Then testing could be something like :
class FooViewModelTest {
#ExperimentalCoroutinesApi
#Test fun `whenObservingUiState thenCorrectStatesObserved`() = runTest {
val states = mutableListOf<UiState>()
FooViewModel { emptyList() }
.uiState
.take(2)
.toList(states)
assertEquals(2, states.size)
assertEquals(listOf(FetchingFoo, FoundFoo(emptyList()), states)
}
#ExperimentalCoroutinesApi
#Test fun `whenObservingUiStateAndErrorOccurs thenCorrectStatesObserved`() = runTest {
val states = mutableListOf<UiState>()
FooViewModel { throw IllegalStateException() }
.uiState
.take(2)
.toList(states)
assertEquals(2, states.size)
assertEquals(listOf(FetchingFoo, EmptFoo), states)
}
}
addotional test dependencies :
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.1'
testImplementation "android.arch.core:core-testing:1.1.1"

Related

Android ViewModel Coroutines launch Test not waiting

I'm trying to do ViewModel testing using Kotlin(1.6.21) Coroutines(1.6.4) and Kotlin Flow.
Following official Kotlin coroutine testing documentation but ViewModel is not waiting/returning a result for suspending functions before test completion. Have gone through top StackOverflow answers and tried all suggested solutions like injecting the same CoroutineDispatcher, and passing the same CoroutineScope but none worked so far. So here I am posting the current simple test implementation. Have to post all classes code involved in the test case to get a better idea.
ReferEarnDetailViewModel.kt:
Injected Usecase and CoroutineContextProvider and calling API using viewModelScope with provided dispatcher. But after calling callReferEarnDetails() from the test case, it is not collecting any data emitted by the mock use case method. Have tried with the direct repo method call, without Kotlin flow as well but same failure.
#HiltViewModel class
ReferEarnDetailViewModel #Inject constructor(
val appDatabase: AppDatabase?,
private val referEarnDetailsUseCase: ReferEarnDetailsUseCase,
private val coroutineContextProvider: CoroutineContextProvider) : BaseViewModel() {
fun callReferEarnDetails() {
setProgress(true)
viewModelScope.launch(coroutineContextProvider.default + handler) {
referEarnDetailsUseCase.execute(UrlUtils.getUrl(R.string.url_referral_detail))
.collect { referEarnDetail ->
parseReferEarnDetail(referEarnDetail)
}
}
}
private fun parseReferEarnDetail(referEarnDetail:
ResultState<CommonEntity.CommonResponse<ReferEarnDetailDomain>>) {
when (referEarnDetail) {
is ResultState.Success -> {
setProgress(false)
.....
}
}
}
ReferEarnCodeUseCase.kt: Returning Flow of Api response.
#ViewModelScoped
class ReferEarnCodeUseCase #Inject constructor(private val repository:
IReferEarnRepository) :BaseUseCase {
suspend fun execute(url: String):
Flow<ResultState<CommonEntity.CommonResponse<ReferralCodeDomain>>> {
return repository.getReferralCode(url)
}
}
CoroutineTestRule.kt
#ExperimentalCoroutinesApi
class CoroutineTestRule(val testDispatcher: TestDispatcher =
StandardTestDispatcher()) : TestWatcher() {
val testCoroutineDispatcher = object : CoroutineContextProvider {
override val io: CoroutineDispatcher
get() = testDispatcher
override val default: CoroutineDispatcher
get() = testDispatcher
override val main: CoroutineDispatcher
get() = testDispatcher
}
override fun starting(description: Description?) {
super.starting(description)
Dispatchers.setMain(testDispatcher)
}
override fun finished(description: Description?) {
super.finished(description)
Dispatchers.resetMain()
}
}
ReferEarnDetailViewModelTest.kt
#RunWith(JUnit4::class)
#ExperimentalCoroutinesApi
class ReferEarnDetailViewModelTest {
private lateinit var referEarnDetailViewModel: ReferEarnDetailViewModel
private lateinit var referEarnDetailsUseCase: ReferEarnDetailsUseCase
#get:Rule
val coroutineTestRule = CoroutineTestRule()
#Mock
lateinit var referEarnRepository: IReferEarnRepository
#Mock
lateinit var appDatabase: AppDatabase
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
referEarnDetailsUseCase = ReferEarnDetailsUseCase(referEarnRepository)
referEarnDetailViewModel = ReferEarnDetailViewModel(appDatabase,
referEarnDetailsUseCase , coroutineTestRule.testCoroutineDispatcher)
}
#Test
fun `test api response parsing`() = runTest {
val data = ResultState.Success( TestResponse() )
//When
Mockito.`when`(referEarnDetailsUseCase.execute("")).thenReturn(flowOf(data))
//Call ViewModel function which further call usecase function.
referEarnDetailViewModel.callReferEarnDetails()
//This should be false after API success response but failing here....
assertEquals(referEarnDetailViewModel.showProgress.get(),false)
}
}
Have tried this solution:
How test a ViewModel function that launch a viewModelScope coroutine? Android
Kotlin
Inject and determine CoroutineScope on ViewModel creation
As it is stated in the documentation runTest awaits completion of all the launched in its TestScope coroutines (or throws a timeout). But it does so on exit from the test body. In your case assertEquals fails inside the test body, so test fails immediately.
Generally speaking, this mechanism of awaiting completion of all jobs is a mean of preventing leaks and is not suitable for your purpose.
There are two ways to control the coroutines execution inside the test body:
Use methods to control virtual time. E.g. advanceUntilIdle should help in this case - use it before asserting the result and it will execute all the tasks scheduled on the given TestDispatcher.
Use regular ways to await execution, e.g. return a job and await its' completion before checking the result. This requires some code redesign, but this is a recommended approach. Check out a couple of paragraphs above the Setting the Main dispatcher chapter.

Passing errors coming from the API call

I am using 2 separate liveData exposed to show the error coming from the API. I am basically checking if there is an exception with the API call, pass a failure status and serverErrorLiveData will be observed.
So I have serverErrorLiveData for error and creditReportLiveData for result without an error.
I think I am not doing this the right way. Could you please guide me on what is the right way of catching error from the API call. Also, any concerns/recommendation on passing data from repository on to view model.
What is the right way of handing loading state?
CreditScoreFragment
private fun initViewModel() {
viewModel.getCreditReportObserver().observe(viewLifecycleOwner, Observer<CreditReport> {
showScoreUI(true)
binding.score.text = it.creditReportInfo.score.toString()
binding.maxScoreValue.text = "out of ${it.creditReportInfo.maxScoreValue}"
initDonutView(
it.creditReportInfo.score.toFloat(),
it.creditReportInfo.maxScoreValue.toFloat()
)
})
viewModel.getServerErrorLiveDataObserver().observe(viewLifecycleOwner, Observer<Boolean> {
if (it) {
showScoreUI(false)
showToastMessage()
}
})
viewModel.getCreditReport()
}
MainActivityViewModel
class MainActivityViewModel #Inject constructor(
private val dataRepository: DataRepository
) : ViewModel() {
var creditReportLiveData: MutableLiveData<CreditReport>
var serverErrorLiveData: MutableLiveData<Boolean>
init {
creditReportLiveData = MutableLiveData()
serverErrorLiveData = MutableLiveData()
}
fun getCreditReportObserver(): MutableLiveData<CreditReport> {
return creditReportLiveData
}
fun getServerErrorLiveDataObserver(): MutableLiveData<Boolean> {
return serverErrorLiveData
}
fun getCreditReport() {
viewModelScope.launch(Dispatchers.IO) {
val response = dataRepository.getCreditReport()
when(response.status) {
CreditReportResponse.Status.SUCCESS -> creditReportLiveData.postValue(response.creditReport)
CreditReportResponse.Status.FAILURE -> serverErrorLiveData.postValue(true)
}
}
}
}
DataRepository
class DataRepository #Inject constructor(
private val apiServiceInterface: ApiServiceInterface
) {
suspend fun getCreditReport(): CreditReportResponse {
return try {
val creditReport = apiServiceInterface.getDataFromApi()
CreditReportResponse(creditReport, CreditReportResponse.Status.SUCCESS)
} catch (e: Exception) {
CreditReportResponse(null, CreditReportResponse.Status.FAILURE)
}
}
}
ApiServiceInterface
interface ApiServiceInterface {
#GET("endpoint.json")
suspend fun getDataFromApi(): CreditReport
}
CreditScoreResponse
data class CreditReportResponse constructor(val creditReport: CreditReport?, val status: Status) {
enum class Status {
SUCCESS, FAILURE
}
}
It's creates complexity and increased chances for a coding error to have two LiveData channels for success and failure. You should have a single LiveData that can offer up the data or an error so you know it's coming in orderly and you can observe it in one place. Then if you add a retry policy, for example, you won't risk somehow showing an error after a valid value comes in. Kotlin can facilitate this in a type-safe way using a sealed class. But you're already using a wrapper class for success and failure. I think you can go to the source and simplify it. You can even just use Kotlin's own Result class.
(Side note, your getCreditReportObserver() and getServerErrorLiveDataObserver() functions are entirely redundant because they simply return the same thing as a property. You don't need getter functions in Kotlin because properties basically are getter functions, with the exception of suspend getter functions because Kotlin doesn't support suspend properties.)
So, to do this, eliminate your CreditReportResponse class. Change your repo function to:
suspend fun getCreditReport(): Result<CreditReport> = runCatching {
apiServiceInterface.getDataFromApi()
}
If you must use LiveData (I think it's simpler not to for a single retrieved value, see below), your ViewModel can look like:
class MainActivityViewModel #Inject constructor(
private val dataRepository: DataRepository
) : ViewModel() {
val _creditReportLiveData = MutableLiveData<Result<CreditReport>>()
val creditReportLiveData: LiveData<Result<CreditReport>> = _creditReportLiveData
fun fetchCreditReport() { // I changed the name because "get" implies a return value
// but personally I would change this to an init block so it just starts automatically
// without the Fragment having to manually call it.
viewModelScope.launch { // no need to specify dispatcher to call suspend function
_creditReportLiveData.value = dataRepository.getCreditReport()
}
}
}
Then in your fragment:
private fun initViewModel() {
viewModel.creditReportLiveData.observe(viewLifecycleOwner) { result ->
result.onSuccess {
showScoreUI(true)
binding.score.text = it.creditReportInfo.score.toString()
binding.maxScoreValue.text = "out of ${it.creditReportInfo.maxScoreValue}"
initDonutView(
it.creditReportInfo.score.toFloat(),
it.creditReportInfo.maxScoreValue.toFloat()
)
}.onFailure {
showScoreUI(false)
showToastMessage()
}
viewModel.fetchCreditReport()
}
Edit: the below would simplify your current code, but closes you off from being able to easily add a retry policy on failure. It might make better sense to keep the LiveData.
Since you are only retrieving a single value, it would be more concise to expose a suspend function instead of LiveData. You can privately use a Deferred so the fetch doesn't have to be repeated if the screen rotates (the result will still arrive and be cached in the ViewModel). So I would do:
class MainActivityViewModel #Inject constructor(
private val dataRepository: DataRepository
) : ViewModel() {
private creditReportDeferred = viewModelScope.async { dataRepository.getCreditReport() }
suspend fun getCreditReport() = creditReportDeferred.await()
}
// In fragment:
private fun initViewModel() = lifecycleScope.launch {
viewModel.getCreditReport()
.onSuccess {
showScoreUI(true)
binding.score.text = it.creditReportInfo.score.toString()
binding.maxScoreValue.text = "out of ${it.creditReportInfo.maxScoreValue}"
initDonutView(
it.creditReportInfo.score.toFloat(),
it.creditReportInfo.maxScoreValue.toFloat()
)
}.onFailure {
showScoreUI(false)
showToastMessage()
}
}

MVVM architecture with Interactors/UseCases

Context
So, I've been working with the MVVM architecture just for a couple of projects. I'm still trying to figure out and improve how the architecture works. I always worked with the MVP architecture, using the usual toolset, Dagger for DI, usually multi-module projects, the Presenter layer being injected with a bunch of Interactors/UseCases, and each Interactor being injected with different Repositories to perform the backend API calls.
Now that I've moved into MVVM I changed the Presenter layer by the ViewModel, the communication from the ViewModel to the UI layer is being done through LiveData instead of using a View callback interface, and so on.
Looks like this:
class ProductDetailViewModel #inject constructor(
private val getProductsUseCase: GetProductsUseCase,
private val getUserInfoUseCase: GetUserInfoUseCase,
) : ViewModel(), GetProductsUseCase.Callback, GetUserInfoUseCase.Callback {
// Sealed class used to represent the state of the ViewModel
sealed class ProductDetailViewState {
data class UserInfoFetched(
val userInfo: UserInfo
) : ProductDetailViewState(),
data class ProductListFetched(
val products: List<Product>
) : ProductDetailViewState(),
object ErrorFetchingInfo : ProductDetailViewState()
object LoadingInfo : ProductDetailViewState()
}
...
// Live data to communicate back with the UI layer
val state = MutableLiveData<ProductDetailViewState>()
...
// region Implementation of the UseCases callbacks
override fun onSuccessfullyFetchedProducts(products: List<Product>) {
state.value = ProductDetailViewState.ProductListFetched(products)
}
override fun onErrorFetchingProducts(e: Exception) {
state.value = ProductDetailViewState.ErrorFetchingInfo
}
override fun onSuccessfullyFetchedUserInfo(userInfo: UserInfo) {
state.value = ProductDetailViewState.UserInfoFetched(userInfo)
}
override fun onErrorFetchingUserInfo(e: Exception) {
state.value = ProductDetailViewState.ErrorFetchingInfo
}
// Functions to call the UseCases from the UI layer
fun fetchUserProductInfo() {
state.value = ProductDetailViewState.LoadingInfo
getProductsUseCase.execute(this)
getUserInfoUseCase.execute(this)
}
}
There's no rocket science here, sometimes I change the implementation to use more than one LiveData property to keep track of the changes. By the way, this is just an example that I wrote on the fly, so don't expect it to compile. But It's just this, the ViewModel is injected with a bunch of UseCases, it implements the UseCases callback interfaces and when I get the results from the UseCases I communicate that to the UI layer through LiveData.
My UseCases usually look like this:
// UseCase interface
interface GetProductsUseCase {
interface Callback {
fun onSuccessfullyFetchedProducts(products: List<Product>)
fun onErrorFetchingProducts(e: Exception)
}
fun execute(callback: Callback)
}
// Actual implementation
class GetProductsUseCaseImpl(
private val productRepository: ApiProductRepostory
) : GetProductsUseCase {
override fun execute(callback: Callback) {
productRepository.fetchProducts() // Fetches the products from the backend through Retrofit
.subscribe(
{
// onNext()
callback.onSuccessfullyFetchedProducts(it)
},
{
// onError()
callback.onErrorFetchingProducts(it)
}
)
}
}
My Repository classes are usually wrappers for the Retrofit instance and they take care of setting the proper Scheduler so everything runs on the proper thread and mapping the backend responses into model classes. By backend responses I mean classes mapped with Gson (for example
a list of ApiProductResponse) and they get mapped into model classes (for example a List of Product which I use across the App)
Question
My question here is that since I started working with the MVVM architecture all the articles and all the examples, people is either injecting the Repositories right into the ViewModel (duplicating code to handle errors and mapping the responses) or either using the Single Source of Truth pattern (getting the information from Room using Room's Flowables). But I haven't seen anyone use UseCases with a ViewModel layer. I mean it's pretty handy, I get to keep things separated, I do the mapping of the backend responses within the UseCases, I handle any error there. But still, feels odds that I don't see anyone doing this, is there some way to improve the UseCases to make them more friendly to the ViewModels in terms of API? Perform the communication between the UseCases and the ViewModels with something else than a callback interface?
Please let me know if you need any more info about this. Sorry for the examples, I know that these are not the best, I just came out with something simple for sake of explaining it better.
Thanks,
Edit #1
This is how my Repository classes look like:
// ApiProductRepository interface
interface ApiProductRepository {
fun fetchProducts(): Single<NetworkResponse<List<ApiProductResponse>>>
}
// Actual implementation
class ApiProductRepositoryImpl(
private val retrofitApi: ApiProducts, // This is a Retrofit API interface
private val uiScheduler: Scheduler, // AndroidSchedulers.mainThread()
private val backgroundScheduler: Scheduler, // Schedulers.io()
) : GetProductsUseCase {
override fun fetchProducts(): Single<NetworkResponse<List<ApiProductResponse>>> {
return retrofitApi.fetchProducts() // Does the API call using the Retrofit interface. I've the RxAdapter set.
.wrapOnNetworkResponse() // Extended function that converts the Retrofit's Response object into a NetworkResponse class
.observeOn(uiScheduler)
.subscribeOn(backgroundScheduler)
}
}
// The network response class is a class that just carries the Retrofit's Response class status code
Update your use case so that it returns Single<List<Product>>:
class GetProducts #Inject constructor(private val repository: ApiProductRepository) {
operator fun invoke(): Single<List<Product>> {
return repository.fetchProducts()
}
}
Then, update your ViewModel so that it subscribes to the products stream:
class ProductDetailViewModel #Inject constructor(
private val getProducts: GetProducts
): ViewModel() {
val state: LiveData<ProductDetailViewState> get() = _state
private val _state = MutableLiveData<ProductDetailViewState>()
private val compositeDisposable = CompositeDisposable()
init {
subscribeToProducts()
}
override fun onCleared() {
super.onCleared()
compositeDisposable.clear()
}
private fun subscribeToProducts() {
getProducts()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.main())
.subscribe(
{
// onNext()
_state.value = ProductListFetched(products = it)
},
{
// onError()
_state.value = ErrorFetchingInfo
}
).addTo(compositeDisposable)
}
}
sealed class ProductDetailViewState {
data class ProductListFetched(
val products: List<Product>
): ProductDetailViewState()
object ErrorFetchingInfo : ProductDetailViewState()
}
One thing I'm leaving out it is the adaptation of List<ApiProductResponse>> to List<Product> but that can be handled by mapping the list with a helper function.
I have just started using MVVM for the last 2 of my projects. I can share with you my process of dealing with REST APIs in ViewModel. Hope it will help you and others.
Make a Generic Retrofit Executer Class with their callbacks. which will take a retrofit call object and gives you data.
Make a repository for Your particular package or module where you can handle all API request. in my case, I am getting one user by its id from API.
Here is User Repository.
class UserRepository {
#Inject
lateinit var mRetrofit: Retrofit
init {
MainApplication.appComponent!!.inject(this)
}
private val userApi = mRetrofit.create(UserApi::class.java)
fun getUserbyId(id: Int): Single<NetworkResponse<User>> {
return Single.create<NetworkResponse<User>>{
emitter ->
val callbyId = userApi.getUserbyId(id)
GenericReqExecutor(callbyId).executeCallRequest(object : ExecutionListener<User>{
override fun onSuccess(response: User) {
emitter.onSuccess(NetworkResponse(success = true,
response = response
))
}
override fun onApiError(error: NetworkError) {
emitter.onSuccess(NetworkResponse(success = false,
response = User(),
networkError = error
))
}
override fun onFailure(error: Throwable) {
emitter.onError(error)
}
})
}
}
}
Then Use this Repository in your ViewModel. In my case here is my LoginViewModel code.
class LoginViewModel : ViewModel() {
var userRepo = UserRepository()
fun getUserById(id :Int){
var diposable = userRepo.getUserbyId(id).subscribe({
//OnNext
},{
//onError
})
}
}
I hope this approach can help you to reduce some of your boilerplate code.
Thanks
I had the same question when I started using MVVM a while ago. I came up with the following solution, based on Kotlin suspend functions and coroutines:
Change ApiProductRepositoryImpl.fetchProducts() to run synchronously. To do this, change your retrofit interface to return Call<...> and then change the repository implementation to
// error handling omitted for brevity
override fun fetchProducts() = retrofitApi.fetchProducts().execute().body()
Make your use cases implement the following interface:
interface UseCase<InputType, OutputType> {
suspend fun execute(input: InputType): OutputType
}
so your GetProductsUseCase would look like this:
class GetProductsUseCase: UseCase<Unit, List<Product>> {
suspend fun execute(input: Unit): List<Product> = withContext(Dispatchers.IO){
// withContext causes this block to run on a background thread
return#withContext productRepository.fetchProducts()
}
Execute the use case in your ViewModel
launch {
state.value = ProductDetailViewState.ProductListFetched(getProductsUseCase.execute())
}
See https://github.com/snellen/umvvm for more info and examples.

Mocking another parameter on function not working when one of them is a callback interface

I successfully did some tests of asynchronous function with only one callback interface as parameter with mockito-kotlin library but when I try to do a test of same function with another parameter like a String or Integer I receive error:
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers!
2 matchers expected, 1 recorded:
-> at com.example.presentation.presenter.MyCollectionPresenterTest.getComicListByHeroOK(MyCollectionPresenterTest.kt:97)
This exception may occur if matchers are combined with raw values:
//incorrect:
someMethod(anyObject(), "raw String");
When using matchers, all arguments have to be provided by matchers.
For example:
//correct:
someMethod(anyObject(), eq("String by matcher"));
For more info see javadoc for Matchers class.
I´m sure I´m mocking properly callback interface with any() but I don´t know if I´m mocking integer parameter correctly. I tried with any(), anyInt(), eq(1) and any() as Int but always the same error.
Here is the class that I want to test:
#PerFragment
class MyCollectionPresenter #Inject constructor(private val useCase: GetComicListByHeroUseCase) {
#Inject
lateinit var view: MyCollectionView
lateinit var models: List<ComicModel>
fun getComicListByHero(heroId: Int) {
useCase.execute(heroId, object : HeroUseCase.GetComicListByHeroCallback {
override fun onComicListReceived(comicList: List<Comic>) {
models = ComicModelMapper.toModel(comicList)
view.setItems(models)
}
override fun onError() {
view.showMessage()
}
})
}
}
And this is the test class:
class MyCollectionPresenterTest : UnitTest() {
private lateinit var presenter: MyCollectionPresenter
#Mock
private lateinit var useCase: GetComicListByHeroUseCase
#Mock
private lateinit var view: MyCollectionView
#Before
fun setUp() {
presenter = MyCollectionPresenter(useCase)
initializeView()
}
#Test
fun getComicListByHeroOK() {
setupGetComicsCallbackOK()
presenter.getComicListByHero(any())
verify(presenter.view).setItems(emptyList())
}
#Test
fun getComicListByHeroError() {
setupGetComicsCallbackError()
presenter.getComicListByHero(any())
verify(presenter.view).showMessage()
}
private fun initializeView() {
presenter.view = view
}
private fun setupGetComicsCallbackError() {
doAnswer {
val callback = it.arguments[0] as HeroUseCase.GetComicListByHeroCallback
callback.onError()
null
}.`when`(useCase).execute(any(), any())
}
private fun setupGetComicsCallbackOK() {
doAnswer {
val callback = it.arguments[0] as HeroUseCase.GetComicListByHeroCallback
callback.onComicListReceived(emptyList())
null
}.`when`(useCase).execute(any(), any())
}
}
This is base unit test class:
#RunWith(MockitoJUnitRunner::class)
abstract class UnitTest {
#Suppress("LeakingThis")
#Rule
#JvmField
val injectMocks = InjectMocksRule.create(this#UnitTest)
}
And this is a class for inject mocks rule:
class InjectMocksRule {
companion object {
fun create(testClass: Any) = TestRule { statement, _ ->
MockitoAnnotations.initMocks(testClass)
statement
}
}
}
Thank you very much for your help and excuse my english.
Regards!
UPDATE: I found the solution and posted as answer.
Finally, I know what I was doing wrong. First at all, it.argument[1] because callback is the second parameter of the function that I want to mock the answer.
And the other thing is that I was mocking the parameter of the function that I want to test presenter.getComicListByHero(1).
Here is the revised code:
#Test
fun getComicListByHeroError() {
setupGetComicsCallbackError()
presenter.getComicListByHero(1)
verify(presenter.view).showMessage()
}
private fun setupGetComicsCallbackError() {
doAnswer {
val callback = it.arguments[1] as HeroUseCase.GetComicListByHeroCallback
callback.onError()
null
}.`when`(useCase).execute(ArgumentMatchers.anyInt(), any())
}
Thank you very much to #voghDev for his help

Android LiveData in androidTest returns null

I'm running an androidTest instrumentation test and I have a method that returns LiveData from a DAO object using Room.
I'm calling the method like so:
val animal = roomDatabase.animalsDao().getAnimal(1)
animal.observeForever(mMockObserver)
assertNotNull(animal.value)
I used Mockito to mock the observer:
#Mock
private lateinit var mMockObserver = Observer<Animal>
This should return an instance of LiveData containing the Animal at id 1, but it's null. It's my understanding that in order for LiveData to return anything, there must be an observer. Did I set this up incorrectly?
Note: If I change the signature of getAnimal() in the DAO to return an Animal directly, rather than a LiveData, then it works so I know it's something with LiveData.
After a little more digging I've found a utility method Google provided through their Architecture Components examples on GitHub.
LiveDataTestUtil
public class LiveDataTestUtil {
/**
* Get the value from a LiveData object. We're waiting for LiveData to emit, for 2 seconds.
* Once we got a notification via onChanged, we stop observing.
*/
public static <T> T getValue(final LiveData<T> liveData) throws InterruptedException {
final Object[] data = new Object[1];
final CountDownLatch latch = new CountDownLatch(1);
Observer<T> observer = new Observer<T>() {
#Override
public void onChanged(#Nullable T o) {
data[0] = o;
latch.countDown();
liveData.removeObserver(this);
}
};
liveData.observeForever(observer);
latch.await(2, TimeUnit.SECONDS);
//noinspection unchecked
return (T) data[0];
}
}
This allows you to pass the LiveData instance and get back the value it holds.
Update (JUnit 4):
You can also use the InstantTaskExecutorRule combined with observeForever to test your LiveData. In Kotlin you set #get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule() at the top of your test class to ensure LiveData is handled synchronously, then inside your test cases myLiveData.observeForever { /* Do something when event emitted */ } to get the LiveData value.
Update (JUnit 5)
If you're using JUnit5, then you can use this extension instead of the Rule explained in Update (JUnit4) above.
class InstantTaskExecutorExtension : BeforeEachCallback, AfterEachCallback {
override fun beforeEach(context: ExtensionContext?) {
ArchTaskExecutor.getInstance().setDelegate(object : TaskExecutor() {
override fun executeOnDiskIO(runnable: Runnable) {
runnable.run()
}
override fun postToMainThread(runnable: Runnable) {
runnable.run()
}
override fun isMainThread(): Boolean {
return true
}
})
}
override fun afterEach(context: ExtensionContext?) {
ArchTaskExecutor.getInstance().setDelegate(null)
}
}
Use this extension by annotating your test class like so:
#ExtendWith(InstantTaskExecutorExtension::class)
class MyTestClass { ... }
If you're new to extensions (they replace JUnit 4 Rules), you can find additional documentation here: https://junit.org/junit5/docs/current/user-guide/#extensions
If you are doing Kotlin, rather than Java, then you can also use:
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
// Original Java: https://github.com/googlesamples/android-architecture-components/blob/master/BasicSample/app/src/androidTest/java/com/example/android/persistence/LiveDataTestUtil.java
object LiveDataTestUtil {
/**
* Get the value from a LiveData object. We're waiting for LiveData to emit, for 2 seconds.
* Once we got a notification via onChanged, we stop observing.
*/
#Throws(InterruptedException::class)
fun <T> getValue(liveData: LiveData<T>): T? {
val data = arrayOfNulls<Any>(1)
val latch = CountDownLatch(1)
val observer: Observer<T?> = object : Observer<T?> {
override fun onChanged(o: T?) {
data[0] = o
latch.countDown()
liveData.removeObserver(this)
}
}
liveData.observeForever(observer)
latch.await(2, TimeUnit.SECONDS)
#Suppress("UNCHECKED_CAST")
return data[0] as T?
}
}
(At the moment the feature of A/S for automigration of Java to Kotlin doesn't quite work correctly for the Google class)
An example for part "Update (JUnit 4)" in Programmer001's answer. (Tuned with official doc on purpose)
Database item data class and table definition:
#Entity(tableName = "item")
data class Item (
var name: String,
#PrimaryKey(autoGenerate = true) var id: Int = 0
)
Test class:
package ...
import ...
#RunWith(AndroidJUnit4::class)
class DBInstrumentedTest1 {
#get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
private lateinit var db: DB
#Before
private fun createDb(): DB {
db = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
DB::class.java
).build()
}
#After
#Throws(IOException::class)
fun closeDb() {
db.close()
}
#Test
#Throws(Exception::class)
fun coroutine_livedata_db_tests_work() {
val itemDao = db.getItemDao()
val item = Item(name = "First", id = 1)
runBlocking(Dispatchers.Default) { itemDao.insert(Item(item.name)) }
itemDao.getItemByName(item.name).asLiveData().observeForever {
assertThat(it, equalTo(item))
}
}
}

Categories

Resources