簡體   English   中英

Android ViewModel 協程啟動測試不等待

[英]Android ViewModel Coroutines launch Test not waiting

我正在嘗試使用 Kotlin(1.6.21) Coroutines(1.6.4) 和 Kotlin Flow 進行 ViewModel 測試。

遵循官方Kotlin 協程測試文檔,但ViewModel 在測試完成之前不會等待/返回暫停功能的結果 已經瀏覽了 StackOverflow 的頂級答案,並嘗試了所有建議的解決方案,例如注入相同的CoroutineDispatcher和傳遞相同的CoroutineScope但到目前為止都沒有。 所以在這里我發布當前的簡單測試實現。 必須發布測試用例中涉及的所有類代碼以獲得更好的想法。

參考EarnDetailViewModel.kt:
注入 Usecase 和 CoroutineContextProvider 並使用 viewModelScope 和提供的調度程序調用 API。 但是在從測試用例調用callReferEarnDetails()之后,它不會收集模擬用例方法發出的任何數據。 已嘗試使用直接回購方法調用,也沒有 Kotlin 流,但同樣失敗。

@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:Api響應的返回流。

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

參考EarnDetailViewModelTest.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)
   }
 }

試過這個解決方案:

  1. 如何測試啟動 viewModelScope 協程的 ViewModel function? Android Kotlin
  2. 在創建 ViewModel 時注入並確定 CoroutineScope

您是否在advanceUntilIdle()之后嘗試了 advanceUntilIdle referEarnDetailViewModel.callReferEarnDetails()

正如文檔中所述, runTest等待其TestScope協程中所有啟動的完成(或引發超時)。 但它會在退出測試機構時這樣做。 在您的情況下, assertEquals在測試主體內失敗,因此測試立即失敗。

一般來說,這種等待所有工作完成的機制是一種防止泄漏的手段,並不適合你的目的。

有兩種方法可以控制測試主體內的協程執行:

  • 使用方法來控制虛擬時間。 例如, AdvanceUntilIdle在這種情況下應該有所幫助——在斷言結果之前使用它,它將執行給定TestDispatcher上安排的所有任務。
  • 使用常規方法等待執行,例如返回作業並在檢查結果之前等待其完成。 這需要重新設計一些代碼,但這是推薦的方法。 查看設置主調度程序章節上方的幾個段落。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM