单元测试作用域视图模型的最佳方法
Best approach for unit-testing scoped viewmodels
在处理 viewModel 内部的协同程序时,最好使用 viewModel 实现 CoroutineScope
,以便在清除 viewModel 时取消所有协同程序。通常我看到 coroutineContext
被定义为 Dispatchers.Main + _job
这样协程默认在主 UI 线程中执行。通常这是在一个开放的 class 上完成的,这样你所有的 viewModels 都可以扩展它并在没有样板代码的情况下获得范围。
当尝试对所述 viewModels 进行单元测试时出现问题,因为 Dispatchers.Main
不可用并且尝试使用它会引发异常。我正在尝试找到一个好的解决方案,该解决方案不涉及外部库或子视图模型上的太多样板。
我目前的解决方案是将主上下文添加为构造函数参数,并将 Dispatchers.Main
作为默认值。然后在单元测试中,在测试 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
}
我个人从 RxJava2 复制了一个解决方案:如果你的测试 运行s 针对 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());
}
我对协程也做了同样的事情。刚刚创建了一个 class 收集调度程序,但这些调度程序可以更改。
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()
}
}
}
这将保证该块将在调用线程中执行。
这是我发现构造函数注入的唯一替代方法。
在处理 viewModel 内部的协同程序时,最好使用 viewModel 实现 CoroutineScope
,以便在清除 viewModel 时取消所有协同程序。通常我看到 coroutineContext
被定义为 Dispatchers.Main + _job
这样协程默认在主 UI 线程中执行。通常这是在一个开放的 class 上完成的,这样你所有的 viewModels 都可以扩展它并在没有样板代码的情况下获得范围。
当尝试对所述 viewModels 进行单元测试时出现问题,因为 Dispatchers.Main
不可用并且尝试使用它会引发异常。我正在尝试找到一个好的解决方案,该解决方案不涉及外部库或子视图模型上的太多样板。
我目前的解决方案是将主上下文添加为构造函数参数,并将 Dispatchers.Main
作为默认值。然后在单元测试中,在测试 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
}
我个人从 RxJava2 复制了一个解决方案:如果你的测试 运行s 针对 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());
}
我对协程也做了同样的事情。刚刚创建了一个 class 收集调度程序,但这些调度程序可以更改。
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()
}
}
}
这将保证该块将在调用线程中执行。
这是我发现构造函数注入的唯一替代方法。