繁体   English   中英

使用模拟 ViewModel 测试 Android ViewModelProvider

[英]Testing Android ViewModelProvider with a mock ViewModel

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

我已经成功配置了我的 androidTests 以便能够在我的设备测试中使用 Mockito,这部分效果很好。

但是, 官方推荐的调用 ViewModelProvider 或by viewModels<>委托的方式似乎并没有提供注入模拟 ViewModel 的方法。 我宁愿不添加一个完整的 DI 框架只是为了解决文档中的这个遗漏,所以我想知道是否有人有任何成功的例子来提供带有官方 Android 架构组件的模拟 ViewModel,而没有 Dagger 或 Hilt 的额外依赖。

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

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

 viewModel = ViewModelProvider(this, ViewModelFactoryOfFactory.INSTANCE)
    .get(MyViewModel::class.java) 

在哪里:

object ViewModelFactoryOfFactory {

    // The default factory.
    var INSTANCE: ViewModelProvider.Factory = MyViewModelFactory()
        private set

    // To set the factory during tests.
    @VisibleForTesting
    fun setTestFactory(factory: ViewModelProvider.Factory) {
        ViewModelFactoryOfFactory.INSTANCE = factory
    }
}

然后在测试设置中可以:

ViewModelFactoryOfFactory.setTestFactory(mockFactory)

有人可能会争辩说,所有这一切都可以被工厂取代以获得 ViewModel。

另一种选择可能只是使ViewModelProvider.Factory成为 Activity 或片段中的字段/属性,因此它也可以从测试中设置,也允许更好的 memory 管理。

我决定重写by viewModels委托以检查模拟 ViewModels 的 map 中的实例,因此如果找不到 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)
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM