使用模拟ViewModel测试Android ViewModelProvider

使用模拟ViewModel测试Android ViewModelProvider,android,unit-testing,mvvm,Android,Unit Testing,Mvvm,我很高兴能使用新的Android架构组件ViewModel系统,它很好地将活动/片段/布局渲染问题与ViewModel逻辑分离开来。我已经成功地对ViewModel进行了独立的单元测试,现在我想通过为各种状态场景的活动/片段提供模拟ViewModels来尝试一些屏幕截图测试 我已经成功地将我的androidTests配置为能够在我的设备测试中使用Mockito,这一部分非常有效 但是,调用ViewModelProvider或通过viewModels委派的方法似乎并没有提供注入模拟viewMode

我很高兴能使用新的Android架构组件ViewModel系统,它很好地将活动/片段/布局渲染问题与ViewModel逻辑分离开来。我已经成功地对ViewModel进行了独立的单元测试,现在我想通过为各种状态场景的活动/片段提供模拟ViewModels来尝试一些屏幕截图测试

我已经成功地将我的androidTests配置为能够在我的设备测试中使用Mockito,这一部分非常有效

但是,调用ViewModelProvider或通过viewModels委派
的方法似乎并没有提供注入模拟viewModels的方法。我不想仅仅为了解决文档中的这一遗漏而添加一个完整的DI框架,所以我想知道是否有人能够成功地为mocked ViewModels提供官方Android架构组件,而不需要额外的Dagger或Hilt依赖项


从1年前开始,建议使用
ActivityTestRule
并手动控制活动生命周期,但该规则已被弃用,取而代之的是不提供此控制的
activityScenarioRule

您可以使用
ViewModelProvider
,因此,您可以使用模拟替换测试中的
ViewModelProvider.Factory
。例如,通过使用:

viewModel=ViewModelProvider(这是ViewModelFactoryOfFactory.INSTANCE)
.get(MyViewModel::class.java)
其中:

对象视图模型工厂工厂{
//默认工厂。
变量实例:ViewModelProvider.Factory=MyViewModelFactory()
专用设备
//在测试期间设置出厂设置。
@可视性测试
fun setTestFactory(工厂:ViewModelProvider.factory){
ViewModelFactoryOffFactory.INSTANCE=工厂
}
}
然后在测试设置中,可以:

ViewModelFactoryOfFactory.setTestFactory(mockFactory)
有人可能会说,所有这些都可以由工厂来获得ViewModel来代替


另一个选项可以是将
ViewModelProvider.Factory
设置为活动或片段中的字段/属性,这样也可以通过测试进行设置,从而实现更好的内存管理。

我决定重写
by viewModels
委托,以检查模拟viewModels映射中的实例,因此,如果找不到ViewModel,我的活动可以使用普通委托模式并提供自己的工厂

val mockedViewModels = HashMap<Class<*>, ViewModel>()

@MainThread
inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
        noinline factoryProducer: (() -> ViewModelProvider.Factory)? = null
): Lazy<VM> {
    // the production producer
    val factoryPromise = factoryProducer ?: {
        defaultViewModelProviderFactory
    }
    return createMockedViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}
/// ... and similar for the fragment-ktx delegates

/**
 * Wraps the default factoryPromise with one that looks in the mockedViewModels map
 */
fun <VM : ViewModel> createMockedViewModelLazy(
        viewModelClass: KClass<VM>,
        storeProducer: () -> ViewModelStore,
        factoryPromise: () -> ViewModelProvider.Factory
): Lazy<VM> {
    // the mock producer
    val mockedFactoryPromise: () -> ViewModelProvider.Factory = {
        // if there are any mocked ViewModels, return a Factory that fetches them
        if (mockedViewModels.isNotEmpty()) {
            object: ViewModelProvider.Factory {
                override fun <T : ViewModel?> create(modelClass: Class<T>): T {
                    return mockedViewModels[modelClass] as T
                            ?: factoryPromise().create(modelClass)  // return the normal one if no mock found
                }
            }
        } else {
            // if no mocks, call the normal factoryPromise directly
            factoryPromise()
        }
    }

    return ViewModelLazy(viewModelClass, storeProducer, mockedFactoryPromise)
}
val mockedViewModels=HashMap()
@主线
inline fun ComponentActivity.viewModels(
noinline factoryProducer:(()->ViewModelProvider.Factory)?=null
):懒惰{
//生产商
val factoryPromise=factoryProducer?:{
defaultViewModelProviderFactory
}
返回createMockedViewModelLazy(VM::class,{viewModelStore},factoryPromise)
}
/// ... 对于片段ktx委托也类似
/**
*将默认factoryPromise包装为在mockedViewModels映射中查看的factoryPromise
*/
有趣的createMockedViewModelLazy(
viewModelClass:KClass,
storeProducer:()->ViewModelStore,
factoryPromise:()->ViewModelProvider.Factory
):懒惰{
//模拟制作人
val mockedFactoryPromise:()->ViewModelProvider.Factory={
//如果存在任何模拟的ViewModels,请返回获取它们的工厂
if(mockedViewModels.isNotEmpty()){
对象:ViewModelProvider.Factory{
重写趣味创建(modelClass:Class):T{
将mockedViewModels[modelClass]返回为T
?:factoryPromise().create(modelClass)//如果未找到模拟,则返回正常值
}
}
}否则{
//如果没有模拟,请直接致电正常的factoryPromise
工厂承诺
}
}
返回ViewModelLazy(viewModelClass、storeProducer、mockedFactoryPromise)
}