使用模拟 ViewModel 测试 Android ViewModelProvider

huf*_*man 7 android unit-testing mvvm

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

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

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

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

huf*_*man 1

我决定重写by viewModels委托来检查模拟 ViewModel 映射中的实例,因此我的活动可以使用正常的委托模式,并在未找到 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)
}
Run Code Online (Sandbox Code Playgroud)