Понятно, вам придется использовать диспетчер Unconfined
:
val Unconfined: CoroutineDispatcher (source)
Диспетчер сопрограмм, не привязанный к какой-либо конкретной теме. Он выполняет начальное продолжение сопрограммы в текущем кадре вызова и позволяет возобновить сопрограмму в любом потоке, который используется соответствующей функцией приостановки, без необходимости какой-либо конкретной политики потоков. Вложенные сопрограммы, запущенные в этом диспетчере, образуют цикл обработки событий, чтобы избежать переполнения стека.
Образец документации:
withContext(Dispatcher.Unconfined) {
println(1)
withContext(Dispatcher.Unconfined) { // Nested unconfined
println(2)
}
println(3)
}
println("Done")
Для моих тестов ViewModel я передаю контекст сопрограммы конструктору ViewModel, чтобы я мог переключаться между Unconfined
и другими диспетчерами, например. Dispatchers.Main
и Dispatchers.IO
.
Контекст сопрограммы для тестов:
@ExperimentalCoroutinesApi
class TestContextProvider : CoroutineContextProvider() {
override val Main: CoroutineContext = Unconfined
override val IO: CoroutineContext = Unconfined
}
Контекст сопрограммы для фактической реализации ViewModel:
open class CoroutineContextProvider {
open val Main: CoroutineContext by lazy { Dispatchers.Main }
open val IO: CoroutineContext by lazy { Dispatchers.IO }
}
ViewModel:
@OpenForTesting
class SampleViewModel @Inject constructor(
val coroutineContextProvider: CoroutineContextProvider
) : ViewModel(), CoroutineScope {
private val job = Job()
override val coroutineContext: CoroutineContext = job + coroutineContextProvider.Main
override fun onCleared() = job.cancel()
fun fetchData() {
launch {
val response = withContext(coroutineContextProvider.IO) {
repository.fetchData()
}
}
}
}
Обновление
Начиная с версии сопрограммы 1.2.1
вы можете использовать runBlockingTest
:
Зависимость:
def coroutines_version = "1.2.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
например:
@Test
fun `sendViewState() sends displayError`(): Unit = runBlockingTest {
Dispatchers.setMain(Dispatchers.Unconfined)
val apiResponse = ApiResponse.success(data)
whenever(repository.fetchData()).thenReturn(apiResponse)
viewModel.viewState.observeForever(observer)
viewModel.processData()
verify(observer).onChanged(expectedViewStateSubmitError)
}