单元测试作用域视图模型的最佳方法

时间:2018-11-26 10:28:34

标签: android unit-testing kotlin kotlinx.coroutines android-viewmodel

在处理viewModel内部的协程时,最好让该viewModel实现CoroutineScope,以便在清除viewModel时取消所有协程。通常,我看到coroutineContext定义为Dispatchers.Main + _job,因此默认情况下协程在主UI线程中执行。通常,这是在开放类上完成的,因此您所有的viewModels都可以对其进行扩展,而无需样板代码即可获得范围。

当由于Dispatchers.Main不可用而尝试对上述viewModels进行单元测试并且尝试使用它引发异常时,会出现此问题。我正在尝试寻找一个好的解决方案,该解决方案在子viewModel上不涉及外部库或过多的样板。

我当前的解决方案是使用Dispatchers.Main作为默认值添加maincontext作为构造参数。然后在单元测试中,在测试viewModel之前,我将其设置为Dispatchers.Default。我不喜欢这种解决方案,因为它公开了coroutineContext实现细节,以供所有人查看和更改:

open class ScopedViewModel(var maincontext = Dispatchers.Main) : ViewModel(), CoroutineScope {
    private val _job = Job()
    override val coroutineContext: CoroutineContext
        get() = maincontext + _job

    override fun onCleared() {
        super.onCleared()
        _job.cancel()
    }
}
class MyViewModel : ScopedViewModel() {}

在测试中:

fun setup(){
    viewModel = MyViewModel()
    viewModel.maincontext = Dispacther.Default
}

1 个答案:

答案 0 :(得分:2)

我个人从RxJava2复制了一个解决方案:如果您的测试是针对RxJava2流(包括两个或多个不同的调度程序)运行的,那么您肯定希望它们全部在单个线程中运行。 这是通过RxJava2测试完成的方式:

@BeforeClass
public static void prepare() {
    RxJavaPlugins.setComputationSchedulerHandler(scheduler -> Schedulers.trampoline());
    RxJavaPlugins.setIoSchedulerHandler(scheduler -> Schedulers.trampoline());
    RxJavaPlugins.setNewThreadSchedulerHandler(scheduler -> Schedulers.trampoline());
    RxJavaPlugins.setSingleSchedulerHandler(scheduler -> Schedulers.trampoline());
    RxAndroidPlugins.setMainThreadSchedulerHandler(scheduler -> Schedulers.trampoline());
}

我对协程也做了同样的事情。刚刚创建了一个收集调度程序的类,但是可以更改这些调度程序。

object ConfigurableDispatchers {

@JvmStatic
@Volatile
var Default: CoroutineDispatcher = Dispatchers.Default

@JvmStatic
@Volatile
var Main: MainCoroutineDispatcher = Dispatchers.Main

...
}

然后,在@BeforeClass方法中调用

@ExperimentalCoroutinesApi
fun setInstantMainDispatcher() {
    Main = object : MainCoroutineDispatcher() {
        @ExperimentalCoroutinesApi
        override val immediate: MainCoroutineDispatcher
            get() = this

        override fun dispatch(context: CoroutineContext, block: Runnable) {
            block.run()
        }
    }
}

这将确保该块将在调用线程中执行。

这是我发现的唯一的构造方法注入方式。