Единичное тестирование сопрограмм на потоке пользовательского интерфейса

Я использую сопрограммы для выполнения асинхронного вызова на pull для обновления следующим образом:

class DataFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener { // other functions here override fun onRefresh() { loadDataAsync() } private fun loadDataAsync() = async(UI) { swipeRefreshLayout?.isRefreshing = true progressLayout?.showContent() val data = async(CommonPool) { service?.getData() // suspending function }.await() when { data == null -> showError() data.isEmpty() -> progressLayout?.showEmpty(null, parentActivity?.getString(R.string.no_data), null) else -> { dataAdapter?.updateData(data) dataAdapter?.notifyDataSetChanged() progressLayout?.showContent() } } swipeRefreshLayout?.isRefreshing = false } } 

Все здесь отлично работает, когда я на самом деле положил его на устройство. Моя ошибка, пустое состояние и состояние данных обрабатываются хорошо, а производительность – хорошая. Тем не менее, я также пытаюсь выполнить тестирование с помощью Spek. Тест My Spek выглядит следующим образом:

 @RunWith(JUnitPlatform::class) class DataFragmentTest : Spek({ describe("The DataFragment") { var uut: DataFragment? = null beforeEachTest { uut = DataFragment() } // test other functions describe("when onRefresh") { beforeEachTest { uut?.swipeRefreshLayout = mock() uut?.onRefresh() } it("sets swipeRefreshLayout.isRefreshing to true") { verify(uut?.swipeRefreshLayout)?.isRefreshing = true // says no interaction with mock } } } } 

Тест не работает, потому что он говорит, что не было взаимодействия с uut?.swipeRefreshLayout mock. После некоторых экспериментов, похоже, это потому, что я использую контекст пользовательского интерфейса через async(UI) . Если я сделаю это просто регулярным асинхронным, я могу пройти тест, но затем приложение выйдет из строя, потому что я изменяю виды за пределами потока пользовательского интерфейса.

Любые идеи, почему это может произойти? Кроме того, если у кого-то есть лучшие предложения для этого, что сделает его более проверяемым, я все уши.

Благодарю.

EDIT: Забыл упомянуть, что я также пытался обернуть verify и uut?.onRefresh() в runBlocking , но я до сих пор не имел успеха.

Если вы хотите сделать вещи чистыми и подумать об использовании архитектуры MVP в будущем, вы должны понимать, что CourutineContext – это внешняя зависимость, которая должна быть введена через DI или передана вашему ведущему. Подробнее о теме .

Ответ на ваш вопрос прост, вы должны использовать только Unconfined CourutineContext для своих тестов. ( подробнее ) Чтобы упростить создание объекта, например Injection with:

 package com.example object Injection { val uiContext : CourutineContext = UI val bgContext : CourutineContext = CommonPool } 

и в тестовом пакете создать абсолютно тот же объект, но изменить на:

 package com.example object Injection { val uiContext : CourutineContext = Unconfined val bgContext : CourutineContext = Unconfined } 

и внутри вашего класса это будет что-то вроде:

 val data = async(Injection.bgContext) {service?.getData()}.await()