Android ViewModel+StateFlow测试问题:测试无法等待后续值
搜索ViewModel单元测试失败问题解决
问题背景
我实现了一个搜索ViewModel,其中searchPoiUseCase负责与Room数据库交互发起查询请求,测试阶段使用Room.inMemoryDatabaseBuilder创建内存数据库。
ViewModel代码如下:
@HiltViewModel class SearchVm @Inject constructor( private val searchPoiUseCase: SearchPoiUseCase ) : ViewModel() { private val queryState = MutableStateFlow("") @OptIn(FlowPreview::class) val searchScreenState = queryState .filter { it.isNotEmpty() } .debounce(500) .distinctUntilChanged() .map { query -> searchPoiUseCase(SearchPoiUseCase.Params(query)) } .map { result -> if (result.isEmpty()) SearchScreenUiState.NothingFound else SearchScreenUiState.SearchResult(result.map { it.toListUiModel() }) } .stateIn( viewModelScope, SharingStarted.WhileSubscribed(5_000), SearchScreenUiState.None ) fun onSearch(query: String) { queryState.value = query } }
这段逻辑在真机/模拟器上运行完全正常,但单元测试始终无法通过。以下是我的单元测试代码:
@OptIn(ExperimentalCoroutinesApi::class) @HiltAndroidTest @Config(application = HiltTestApplication::class) @RunWith(RobolectricTestRunner::class) class SearchViewModelTest { @get:Rule var hiltRule = HiltAndroidRule(this) @Inject lateinit var searchUseCase: SearchPoiUseCase lateinit var SUT: SearchVm @Before fun setup() { hiltRule.inject() SUT = SearchVm(searchUseCase) Dispatchers.setMain(UnconfinedTestDispatcher()) } @After fun teardown() { Dispatchers.resetMain() } @Test fun `test search view model`() = runTest { val collectJob = launch { SUT.searchScreenState.collect() } assertEquals(SearchScreenUiState.None, SUT.searchScreenState.value) SUT.onSearch("Query") assertEquals(SearchScreenUiState.NothingFound, SUT.searchScreenState.value) collectJob.cancel() } }
测试中的第二个断言始终失败,请问我遗漏了什么?
解决方案
感谢Ibrahim Disouki的思路,我对测试代码做了一处修改后问题解决,修改后的测试代码如下:
@Test fun `test search view model`() = runTest { whenever(searchUseCase(SearchPoiUseCase.Params("Query"))).thenReturn(emptyList()) // 此处可替换为返回有效数据的测试用例 assertEquals(SearchScreenUiState.None, SUT.searchScreenState.value) val job = launch { SUT.searchScreenState.collect() // 确保流被收集 } SUT.onSearch("Query") advanceTimeBy(500) // 跳过debounce设置的500ms延迟 runCurrent() // 执行测试调度器中当前虚拟时间的所有待处理任务 assertEquals(SearchScreenUiState.NothingFound, SUT.searchScreenState.value) job.cancel() }
核心修改点:
- 为
searchUseCase设置模拟返回值,避免依赖真实数据库 - 调用
advanceTimeBy(500)跳过ViewModel中debounce(500)的延迟等待 - 调用
runCurrent()确保所有待处理的流操作都执行完毕
内容的提问来源于stack exchange,提问作者Nikita Grishko
相关产品推荐
相关产品推荐

