Friday, 9 September 2022

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

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

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:

  1. How test a ViewModel function that launch a viewModelScope coroutine? Android Kotlin
  2. Inject and determine CoroutineScope on ViewModel creation


from Android ViewModel Coroutines launch Test not waiting

No comments:

Post a Comment