Android ViewModel协程测试:launch未等待结果问题排查
Kotlin协程+Flow的ViewModel测试问题:测试未等待挂起函数结果完成
我使用Kotlin(1.6.21)、Coroutines(1.6.4)和Kotlin Flow进行ViewModel测试,遵循官方《Kotlin协程测试文档》,但ViewModel在测试完成前不会等待/返回挂起函数的结果。尝试了注入相同CoroutineDispatcher、传递相同CoroutineScope等所有热门方案,但均无效。以下是测试涉及的所有类代码,便于定位问题:
ReferEarnDetailViewModel.kt
注入了UseCase和CoroutineContextProvider,使用viewModelScope结合指定dispatcher调用API。但测试用例调用callReferEarnDetails()后,并未收集到mock用例方法发射的数据。尝试过直接调用仓库方法、不使用Kotlin Flow,结果同样失败。
@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(返回API响应的Flow)
@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)) // 调用ViewModel函数,该函数会进一步调用用例方法 referEarnDetailViewModel.callReferEarnDetails() // API成功响应后此处应为false,但测试失败 assertEquals(referEarnDetailViewModel.showProgress.get(), false) } }
已尝试的解决方案
- 测试启动viewModelScope协程的ViewModel函数的相关方案
- 在ViewModel创建时注入并指定CoroutineScope的相关方案
内容的提问来源于stack exchange,提问作者Satwinder Singh
相关产品推荐
相关产品推荐

