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

MVVM架构下Activity中ViewModel无法Mock的最优解决方案咨询

解决ViewModel无法在Activity中Mock的最优方案

针对你遇到的「ViewModel在Activity的onCreate()中直接实例化导致无法Mock,进而无法完成加载状态Instrument Test」的问题,核心思路是打破ViewModel的硬编码实例化逻辑,提供可替换的依赖入口,下面给出两种适配Google MVVM架构的可行方案:

方案一:使用Dagger Hilt(推荐,适配官方架构规范)

如果你的项目已经或计划使用依赖注入框架,Hilt是官方推荐的选择,它能完美适配ViewModel的注入与测试替换:

1. 改造ViewModel的注入方式

给你的ViewModel添加@HiltViewModel注解,并通过构造函数注入依赖:

@HiltViewModel
class YourViewModel @Inject constructor(
    private val yourRepository: YourRepository
) : ViewModel() {
    // 你的ViewModel业务逻辑,比如加载状态LiveData
    val loadingState = MutableLiveData<Boolean>()
}

2. 修改Activity的ViewModel获取逻辑

给Activity添加@AndroidEntryPoint注解,用Hilt提供的viewModels()委托获取ViewModel,替代原来的硬编码创建:

@AndroidEntryPoint
class YourActivity : AppCompatActivity() {
    private val viewModel: YourViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_your)
        // 绑定ViewModel的LiveData到视图
        viewModel.loadingState.observe(this) { isLoading ->
            progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
        }
    }
}

3. 编写Instrumented Test替换Mock ViewModel

创建测试专用的Hilt模块,替换生产环境的依赖;然后在测试类中注入自定义Factory,生成Mock ViewModel:

@HiltAndroidTest
class YourActivityLoadingTest {
    @get:Rule
    val hiltRule = HiltAndroidRule(this)

    private lateinit var mockViewModel: YourViewModel

    @Before
    fun setup() {
        hiltRule.inject()
        // 用MockK/Mockito创建ViewModel的Mock实例
        mockViewModel = mockk(relaxed = true)
        // 模拟加载状态为true
        coEvery { mockViewModel.loadingState } returns MutableLiveData(true)
    }

    @Test
    fun `when loading state is true, progress bar is displayed`() {
        // 启动Activity并替换ViewModel
        launchActivity<YourActivity>().onActivity { activity ->
            ViewModelProvider(activity, object : ViewModelProvider.Factory {
                override fun <T : ViewModel> create(modelClass: Class<T>): T {
                    return mockViewModel as T
                }
            })[YourViewModel::class.java]
        }

        // 验证进度条可见
        onView(withId(R.id.progress_bar)).check(matches(isDisplayed()))
    }
}

// 测试专用模块,替换生产环境的Repository依赖
@Module
@TestInstallIn(
    components = [SingletonComponent::class],
    replaces = [YourRepositoryModule::class]
)
class TestYourRepositoryModule {
    @Provides
    fun provideMockRepository(): YourRepository {
        return mockk(relaxed = true)
    }
}

方案二:手动实现可注入的ViewModel Factory(无DI框架)

如果暂时不想引入DI框架,可以通过给Activity添加Factory入口的方式,实现ViewModel的可替换:

1. 创建自定义ViewModel Factory

class YourViewModelFactory(private val repository: YourRepository) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(YourViewModel::class.java)) {
            return YourViewModel(repository) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

2. 修改Activity,允许外部传入Factory

给Activity添加一个可修改的Factory变量,优先使用外部传入的实例,否则用默认Factory:

class YourActivity : AppCompatActivity() {
    private lateinit var viewModel: YourViewModel
    // 供测试用的Factory入口
    var viewModelFactory: ViewModelProvider.Factory? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_your)
        val factory = viewModelFactory ?: YourViewModelFactory(YourRepositoryImpl())
        viewModel = ViewModelProvider(this, factory)[YourViewModel::class.java]
        
        // 绑定LiveData到视图
        viewModel.loadingState.observe(this) { isLoading ->
            progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
        }
    }
}

3. 在测试中传入Mock Factory

class YourActivityLoadingTest {
    @Test
    fun `loading state displays progress bar`() {
        // 创建Mock ViewModel并模拟加载状态
        val mockViewModel = mock(YourViewModel::class.java)
        `when`(mockViewModel.loadingState).thenReturn(MutableLiveData(true))

        // 创建返回Mock ViewModel的Factory
        val mockFactory = object : ViewModelProvider.Factory {
            override fun <T : ViewModel> create(modelClass: Class<T>): T {
                return mockViewModel as T
            }
        }

        // 启动Activity并设置Mock Factory
        launchActivity<YourActivity>().onActivity { activity ->
            activity.viewModelFactory = mockFactory
            // 重新初始化ViewModel,覆盖onCreate中创建的实例
            activity.viewModel = ViewModelProvider(activity, mockFactory)[YourViewModel::class.java]
        }

        // 验证进度条可见
        onView(withId(R.id.progress_bar)).check(matches(isDisplayed()))
    }
}

为什么之前的尝试失败?

你之前的问题大概率是因为没有打破ViewModel在Activity内部的硬编码实例化逻辑,测试时无法替换成Mock实例;另外Instrumented Test中Activity的生命周期由框架管理,需要确保在onCreate执行前后能完成ViewModel的替换。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 07:28:42