Лучший подход для модульного тестирования видовых моделей - PullRequest
0 голосов
/ 26 ноября 2018

При работе с сопрограммами внутри viewModel лучше всего указывать, что viewModel реализует CoroutineScope, поэтому все сопрограммы отменяются при очистке viewModel.Обычно я вижу coroutineContext, определенный как Dispatchers.Main + _job, поэтому сопрограммы по умолчанию выполняются в основном потоке пользовательского интерфейса.Обычно это делается для открытого класса, так что все ваши viewModels могут расширить его и получить область без стандартного кода.

Проблема возникает при попытке выполнить модульное тестирование указанных viewModels, так как Dispatchers.Main недоступен и пытаетсяиспользовать это создает исключение.Я пытаюсь найти хорошее решение, которое не включает в себя внешние библиотеки или слишком много наглядной модели дочерних viewModels.

Мое текущее решение состоит в том, чтобы добавить maincontext в качестве параметра-конструктора с 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
}

1 Ответ

0 голосов
/ 26 ноября 2018

Лично я скопировал решение из 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()
        }
    }
}

, который будет гарантировать, что блок будетбыть выполненным в вызывающем потоке.

Это единственная альтернатива, которую я нашел для внедрения в конструктор.

...