Я хочу написать тесты для моего приложения для Android.Иногда viewModel выполняет задачи в фоновом режиме, используя функцию запуска сопрограммы Kotlins.Эти задачи выполняются в viewModelScope, который так легко предоставляет библиотека androidx.lifecycle.Чтобы по-прежнему тестировать эти функции, я заменил диспетчера Android по умолчанию на Dispatchers.Unconfined, который выполняет код синхронно.
По крайней мере, в большинстве случаев.При использовании suspendCoroutine Dispatchers.Unconfined не будет приостановлен и позднее возобновлен, а вместо этого просто вернется.Документация Dispatchers.Unconfined
показывает, почему:
[Dispatchers.Unconfined] позволяет сопрограмме возобновлять работу в любом потоке, который используется соответствующей функцией приостановки.
Насколько я понимаю, сопрограмма фактически не приостанавливается, но остальная часть асинхронной функции после suspendCoroutine
запускается в потоке, который вызывает continuation.resume
.Поэтому тест не пройден.
Пример:
class TestClassTest {
var testInstance = TestClass()
@Test
fun `Test with default dispatchers should fail`() {
testInstance.runAsync()
assertFalse(testInstance.valueToModify)
}
@Test
fun `Test with dispatchers replaced by Unconfined should pass`() {
testInstance.DefaultDispatcher = Dispatchers.Unconfined
testInstance.IODispatcher = Dispatchers.Unconfined
testInstance.runAsync()
assertTrue(testInstance.valueToModify)
}
@Test
fun `I need to also test some functions that use suspend coroutine - How can I do that?`() {
testInstance.DefaultDispatcher = Dispatchers.Unconfined
testInstance.IODispatcher = Dispatchers.Unconfined
testInstance.runSuspendCoroutine()
assertTrue(testInstance.valueToModify)//fails
}
}
class TestClass {
var DefaultDispatcher: CoroutineContext = Dispatchers.Default
var IODispatcher: CoroutineContext = Dispatchers.IO
val viewModelScope = CoroutineScope(DefaultDispatcher)
var valueToModify = false
fun runAsync() {
viewModelScope.launch(DefaultDispatcher) {
valueToModify = withContext(IODispatcher) {
sleep(1000)
true
}
}
}
fun runSuspendCoroutine() {
viewModelScope.launch(DefaultDispatcher) {
valueToModify = suspendCoroutine {
Thread {
sleep(1000)
//long running operation calls listener from background thread
it.resume(true)
}.start()
}
}
}
}
Я экспериментировал с runBlocking
, однако он помогает только при вызове запуска с использованием CoroutineScope
, созданного runBlocking
.Но так как мой код запускается на CoroutineScope
, предоставленном viewModelScope
, это не будет работать.Если это возможно, я бы предпочел не вводить CoroutineScope везде, потому что любой класс более низкого уровня мог бы (и делает) свой собственный CoroutineScope, как в примере, и тогда Test должен знать много о деталях реализации.Основная идея заключается в том, что каждый класс может контролировать отмену своих асинхронных операций.Например, viewModelScope по умолчанию отменяется, когда viewModel уничтожается.
Мой вопрос: Какой диспетчер сопрограмм я мог бы использовать для запуска, запуска с блокировкой контекста и т. Д. (Например, Dispatchers.Unconfined
), а также для блокировки suspendCoroutine