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

如何在Robolectric单元测试中为Dagger2注入的Jetpack ViewModel提供Mock实例?

如何用Robolectric测试带Dagger注入ViewModel的Activity

嘿,我来帮你搞定这个Robolectric测试里Mock ViewModel的问题!你的场景是Activity通过Dagger注入的ViewModelProvider.Factory来获取ViewModel,要做单元测试的话,核心就是把这个Factory替换成咱们自己的Mock版本,让它返回预先定义好的Mock ViewModel。下面给你两种实用的实现方法,你可以根据需求选:

方法一:用反射直接替换Activity里的Factory(快速简单)

这种方法不需要改Dagger的配置,直接在测试里用反射把Activity的vmFactory换成Mock的,适合单个测试场景快速验证。

步骤1:准备Mock对象

用Mockito(或者MockK,看你习惯)创建ActivityViewModel和ViewModelProvider.Factory的Mock实例,指定Factory的create方法返回我们的Mock ViewModel:

@RunWith(RobolectricTestRunner::class)
class ActivityEpisodeListTest {

    @Mock
    private lateinit var mockViewModel: ActivityViewModel
    @Mock
    private lateinit var mockVmFactory: ViewModelProvider.Factory

    @Before
    fun setup() {
        // 初始化Mockito
        MockitoAnnotations.openMocks(this)
        
        // 让Mock Factory返回我们的Mock ViewModel
        `when`(mockVmFactory.create(ActivityViewModel::class.java))
            .thenReturn(mockViewModel)
    }

步骤2:替换Activity的Factory并启动

Robolectric创建Activity时,默认会调用onCreate,所以咱们要先拿到Activity实例,替换vmFactory字段,再手动调用onCreate:

@Test
    fun `activity should use mock view model from factory`() {
        // 创建Activity实例(还没调用onCreate)
        val activity = Robolectric.buildActivity(ActivityEpisodeList::class.java).get()

        // 用反射替换vmFactory字段
        val vmFactoryField = ActivityEpisodeList::class.java.getDeclaredField("vmFactory")
        vmFactoryField.isAccessible = true
        vmFactoryField.set(activity, mockVmFactory)

        // 手动调用onCreate,让Activity初始化ViewModel
        activity.onCreate(null)

        // 验证Activity里的ViewModel确实是咱们的Mock实例
        val vmField = ActivityEpisodeList::class.java.getDeclaredField("vm")
        vmField.isAccessible = true
        val activityVm = vmField.get(activity) as ActivityViewModel
        assertThat(activityVm).isEqualTo(mockViewModel)

        // 这里可以继续验证业务逻辑,比如如果Activity在onCreate里调用了vm.loadEpisodes(),可以加:
        // verify(mockViewModel).loadEpisodes()
    }
}

方法二:用Dagger测试组件替换依赖(更贴近真实注入)

如果你的测试需要多个依赖替换,或者想更贴近真实的注入流程,可以创建一个测试用的Dagger组件,绑定Mock ViewModel。

步骤1:创建测试Module和Component

写一个Test版本的ViewModelModule,绑定Mock的ActivityViewModel,再创建对应的Test AppComponent:

// 测试用的ViewModelModule,绑定Mock ViewModel
@Module
abstract class TestViewModelModule {
    @Binds
    internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory

    @Binds
    @IntoMap
    @ViewModelKey(ActivityViewModel::class)
    internal abstract fun postListViewModel(viewModel: ActivityViewModel): ViewModel
}

// 测试用的AppComponent,继承自原来的AppComponent
@Singleton
@Component(modules = [TestViewModelModule::class])
interface TestAppComponent : AppComponent {
    fun inject(test: ActivityEpisodeListTest)
}

// 测试用的Application类,返回TestAppComponent
class TestMvvmDaggerApp : MvvmDaggerApp() {
    override fun createAppComponent(): AppComponent {
        return DaggerTestAppComponent.builder()
            .viewModelModule(TestViewModelModule())
            .build()
    }
}

步骤2:编写测试用例

在测试里指定用Test Application,然后通过Dagger注入Mock ViewModel,验证Activity使用的是这个实例:

@Config(application = TestMvvmDaggerApp::class)
@RunWith(RobolectricTestRunner::class)
class ActivityEpisodeListTest {

    @Inject
    lateinit var mockViewModel: ActivityViewModel

    @Before
    fun setup() {
        // 从Test Application获取Component并注入测试类
        val testApp = ApplicationProvider.getApplicationContext<TestMvvmDaggerApp>()
        testApp.appComponent.inject(this)
    }

    @Test
    fun `activity should use injected mock view model`() {
        // 启动Activity,Robolectric会自动调用onCreate并完成注入
        val activity = Robolectric.buildActivity(ActivityEpisodeList::class.java)
            .setup()
            .get()

        // 验证Activity的ViewModel和我们注入的Mock实例一致
        val vmField = ActivityEpisodeList::class.java.getDeclaredField("vm")
        vmField.isAccessible = true
        val activityVm = vmField.get(activity) as ActivityViewModel
        assertThat(activityVm).isEqualTo(mockViewModel)
    }
}

两种方法对比

  • 方法一:快速上手,不需要修改Dagger配置,适合单个测试场景或者简单验证。缺点是用了反射,代码稍微有点“hack”感。
  • 方法二:更贴近真实的依赖注入流程,适合复杂测试场景,多个测试用例可以复用Test Component。缺点是需要额外编写测试Module和Component,代码量稍大。

小贴士

  • 如果你用MockK代替Mockito,代码会更简洁,比如创建Mock对象只需val mockViewModel = mockk<ActivityViewModel>(),不需要@Mock注解和MockitoAnnotations.openMocks(this)。
  • 确保Robolectric版本和你的AndroidX、Dagger版本兼容,避免出现奇怪的兼容性问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 03:47:37