Android ViewModel 协程启动测试不等待

Sun*_*man 7 android kotlin android-testing android-viewmodel kotlin-coroutines

我正在尝试使用 Kotlin(1.6.21) Coroutines(1.6.4) 和 Kotlin Flow 进行 ViewModel 测试。

遵循官方Kotlin 协程测试文档,但ViewModel 在测试完成之前不会等待/返回挂起函数的结果。已经浏览了 StackOverflow 的热门答案,并尝试了所有建议的解决方案,例如注入相同的内容CoroutineDispatcher,并传递相同的内容CoroutineScope,但到目前为止没有一个有效。所以在这里我发布当前的简单测试实现。必须发布测试用例中涉及的所有类代码以获得更好的想法。

ReferEarnDetailViewModel.kt:
注入 Usecase 和 CoroutineContextProvider 并使用 viewModelScope 和提供的调度程序调用 API。但是从测试用例调用callReferEarnDetails()后,它不会收集模拟用例方法发出的任何数据。已尝试使用直接 repo 方法调用,也没有 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)
            .....
       }
    }
  }
Run Code Online (Sandbox Code Playgroud)

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)
   }
}
Run Code Online (Sandbox Code Playgroud)

协程测试规则.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()
  }
}
Run Code Online (Sandbox Code Playgroud)

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)
   }
 }
Run Code Online (Sandbox Code Playgroud)

已经尝试过这个解决方案:

  1. 如何测试启动 viewModelScope 协程的 ViewModel 函数?安卓科特林
  2. 在 ViewModel 创建时注入并确定 CoroutineScope

ese*_*sov 0

正如文档中所述,runTest等待其协程中所有启动的完成TestScope(或引发超时)。但它会在退出测试机构时执行此操作。在您的情况下,assertEquals测试体内失败,因此测试立即失败。

一般来说,这种等待所有作业完成的机制是一种防止泄漏的手段,并不适合您的目的。

有两种方法可以控制测试体内协程的执行:

  • 用方法来控制虚拟时间。例如advanceUntilIdle在这种情况下应该有所帮助 -在断言结果之前使用它,它将执行给定的所有计划任务TestDispatcher
  • 使用常规方式等待执行,例如返回作业并在检查结果之前等待其完成。这需要重新设计一些代码,但这是推荐的方法。查看“设置主调度程序”一章上面的几段内容。