Android测试中如何正确Mock ViewModel?
如何Mock ViewModel测试Fragment的列表显示UI单元测试
先把你提供的代码片段整理出来,方便后续参考:
你的FavoritesFragment代码片段
class FavoritesFragment : Fragment() { private lateinit var adapter: FavoritesAdapter private lateinit var viewModel: FavoritesViewModel @Inject lateinit var viewModelFactory: FavoritesViewModelFactory // ... 其他业务代码 }
测试代码基础框架
@MediumTest @RunWith(AndroidJUnit4::class) class FavoritesFragmentTest { // 待完善的测试逻辑 }
接下来我会一步步教你完成Mock ViewModel的列表验证单元测试:
1. 准备测试依赖
确保你的Module级别build.gradle里包含这些测试依赖(Kotlin环境为例):
testImplementation "junit:junit:4.13.2" androidTestImplementation "androidx.test.ext:junit:1.1.5" androidTestImplementation "androidx.test.espresso:espresso-core:3.5.1" androidTestImplementation "org.mockito:mockito-android:4.8.1" androidTestImplementation "org.mockito.kotlin:mockito-kotlin:4.1.0" // 简化Kotlin的Mockito语法
2. 核心测试实现
我们用Mockito来Mock ViewModel和Factory,确保Fragment使用的是模拟实例而非真实对象:
@MediumTest @RunWith(AndroidJUnit4::class) class FavoritesFragmentTest { // Mock核心对象 private val mockViewModel: FavoritesViewModel = mock() private val mockViewModelFactory: FavoritesViewModelFactory = mock() @Before fun setup() { // 让Factory返回我们预先Mock的ViewModel whenever(mockViewModelFactory.create(FavoritesViewModel::class.java)) .thenReturn(mockViewModel) } @Test fun `when viewModel returns favorite items, recyclerView displays correct count and content`() { // 1. 准备模拟数据 val testFavorites = listOf( FavoriteItem(id = 1, title = "First Favorite"), FavoriteItem(id = 2, title = "Second Favorite") ) // 2. 设定ViewModel的LiveData返回值(假设你的ViewModel用LiveData暴露数据) val mockLiveData = MutableLiveData(testFavorites) whenever(mockViewModel.favoritesLiveData).thenReturn(mockLiveData) // 3. 启动Fragment并注入Mock的Factory val fragmentScenario = launchFragmentInContainer<FavoritesFragment>() fragmentScenario.onFragment { fragment -> // 手动注入Factory到Fragment fragment.viewModelFactory = mockViewModelFactory // 手动初始化ViewModel(对应Fragment里的lateinit变量) fragment.viewModel = ViewModelProvider(fragment, mockViewModelFactory)[FavoritesViewModel::class.java] // 触发生命周期,确保ViewModel的LiveData被观察 fragment.viewLifecycleOwner.lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) } // 4. 用Espresso验证UI // 验证RecyclerView的item数量是否匹配 onView(withId(R.id.favorites_recycler_view)) .check(matches(hasChildCount(testFavorites.size))) // 验证第一个item的内容是否正确显示 onView(withText("First Favorite")) .check(matches(isDisplayed())) } }
3. 如果你用Dagger Hilt的适配方案
如果你的Fragment用Hilt注入viewModelFactory,测试需要做以下调整:
- 测试类替换注解:
@HiltAndroidTest @RunWith(HiltTestRunner::class) class FavoritesFragmentTest { // ... }
- 创建TestModule提供Mock的Factory:
@Module @TestInstallIn(components = [SingletonComponent::class], replaces = [FavoritesViewModelModule::class]) object TestFavoritesViewModelModule { @Provides fun provideMockViewModelFactory(): FavoritesViewModelFactory { val mockFactory = mock<FavoritesViewModelFactory>() val mockViewModel = mock<FavoritesViewModel>() whenever(mockFactory.create(FavoritesViewModel::class.java)).thenReturn(mockViewModel) return mockFactory } }
- 测试类中通过
@Inject获取Mock的ViewModel,无需手动注入。
关键注意点
- 确保
FavoritesAdapter在Fragment里正确初始化并绑定到RecyclerView - 如果ViewModel用Flow暴露数据,记得在测试中转换为LiveData或用
runBlockingTest收集数据 launchFragmentInContainer提供模拟Activity环境,适合UI单元测试- Espresso的
matches断言可覆盖item数量、文本内容、控件可见性等多种验证场景
内容的提问来源于stack exchange,提问作者Joaquim Ley
相关产品推荐
相关产品推荐

