You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,测试需要做以下调整:

  1. 测试类替换注解:
@HiltAndroidTest
@RunWith(HiltTestRunner::class)
class FavoritesFragmentTest {
    // ...
}
  1. 创建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
    }
}
  1. 测试类中通过@Inject获取Mock的ViewModel,无需手动注入。

关键注意点

  • 确保FavoritesAdapter在Fragment里正确初始化并绑定到RecyclerView
  • 如果ViewModel用Flow暴露数据,记得在测试中转换为LiveData或用runBlockingTest收集数据
  • launchFragmentInContainer提供模拟Activity环境,适合UI单元测试
  • Espresso的matches断言可覆盖item数量、文本内容、控件可见性等多种验证场景

内容的提问来源于stack exchange,提问作者Joaquim Ley

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 03:49:29