简体   繁体   English

使用模拟 ViewModel 测试 Android ViewModelProvider

[英]Testing Android ViewModelProvider with a mock ViewModel

I'm excited to use the new Android Architecture Components ViewModel system, which nicely separates the Activity/Fragment/Layout rendering concerns from the ViewModel logic.我很高兴使用新的 Android 架构组件 ViewModel 系统,它很好地将 Activity/Fragment/Layout 渲染问题与 ViewModel 逻辑分开。 I've successfully unit-tested the ViewModel in isolation, and now would like to try out some screenshot testing by providing mocked ViewModels to the Activity/Fragment for various state scenarios.我已经成功地对 ViewModel 进行了单独的单元测试,现在想通过为各种 state 场景的 Activity/Fragment 提供模拟的 ViewModel 来尝试一些屏幕截图测试。

I've successfully configured my androidTests to be able to use Mockito in my device tests, that part works great.我已经成功配置了我的 androidTests 以便能够在我的设备测试中使用 Mockito,这部分效果很好。

However, the officially recommended way of calling ViewModelProvider or delegating by viewModels<> does not seem to offer a way to inject mocked ViewModels.但是, 官方推荐的调用 ViewModelProvider 或by viewModels<>委托的方式似乎并没有提供注入模拟 ViewModel 的方法。 I'd rather not add an entire DI framework just to work around this omission in the documentation, so I wonder if anyone has any successful examples of providing mocked ViewModels with the official Android Architecture Components without the extra dependencies of Dagger or Hilt.我宁愿不添加一个完整的 DI 框架只是为了解决文档中的这个遗漏,所以我想知道是否有人有任何成功的例子来提供带有官方 Android 架构组件的模拟 ViewModel,而没有 Dagger 或 Hilt 的额外依赖。

The only related answer from 1 year ago suggests using ActivityTestRule and manually controlling the activity lifecycle, but that Rule is deprecated in favor of activityScenarioRule which does not provide this control. 1 年前唯一相关的答案建议使用ActivityTestRule并手动控制活动生命周期,但该规则已被弃用,取而代之的是不提供此控制的activityScenarioRule

You can use a ViewModelProvider , so you can replace the ViewModelProvider.Factory in the tests with a mock.您可以使用ViewModelProvider ,因此您可以将测试中的ViewModelProvider.Factory替换为模拟。 For example by using:例如通过使用:

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

Where:在哪里:

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
    }
}

Then in the tests setup one can:然后在测试设置中可以:

ViewModelFactoryOfFactory.setTestFactory(mockFactory)

One may argue that all this could be replaced by just the factory to get the ViewModel.有人可能会争辩说,所有这一切都可以被工厂取代以获得 ViewModel。

Another option could be just make the ViewModelProvider.Factory a field/property in the Activity or fragment, so it can be also set from tests, also allowing for better memory management.另一种选择可能只是使ViewModelProvider.Factory成为 Activity 或片段中的字段/属性,因此它也可以从测试中设置,也允许更好的 memory 管理。

I decided to rewrite the by viewModels delegate to check for instances in a map of mock ViewModels, so my activities can use the normal delegate pattern and provide their own factories if the ViewModel isn't found.我决定重写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.

相关问题 通过 ViewModelProvider 获取 ViewModel 实例 - Getting ViewModel Instance by ViewModelProvider 在 VIew 中如何使用 ViewModel 和 ViewModelProvider - In VIew how to use ViewModel with ViewModelProvider NoBeanDefFoundException 与 Mock ViewModel,使用 Koin、Espresso 进行测试 - NoBeanDefFoundException with Mock ViewModel, testing with Koin, Espresso ViewModelProvider.of 和 ViewModelProvider 在 Android Java 中均已弃用 - ViewModelProvider.of and ViewModelProvider both are deprecated in Android Java 在 android 中使用“by viewModels()”与“ViewModelProvider(this).get(ViewModel::class.java)”查看 model 初始化 - View model initialization using “by viewModels()” vs “ViewModelProvider(this).get(ViewModel::class.java)” in android Android ViewModelProvider() 参数错误 - Android ViewModelProvider() parameter error ViewModelProvider.Factory 总是返回一个视图模型 - ViewModelProvider.Factory always return one viewmodel 在 kotlin 中如何将 ViewModel 与 ViewModelProvider.AndroidViewModelFactory 一起使用? - In kotlin how to use ViewModel With ViewModelProvider.AndroidViewModelFactory? 无法从 ViewModelProvider 创建 ViewModel - Can't create ViewModel from ViewModelProvider 使用 ViewModelProvider(this) 初始化 ViewModel 实例时出现问题 - Problem initializing ViewModel instance using ViewModelProvider(this)
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM