
Кotlin Coroutines стали популярним інструментом для роботи з асинхронним кодом в мові програмування Kotlin. Вони дозволяють зручно та ефективно працювати з паралельним виконанням, уникнути блокування потоків і забезпечити зручний синтаксис. Однак, при розробці програм з використанням корутин виникають ситуації, коли потрібно перевірити, які саме диспетчери використовуються у ваших асинхронних операціях, зокрема, під час юніт-тестування. У цій статті ми розглянемо, як правильно перевіряти диспетчери корутин під час юніт-тестування на прикладі Kotlin-класу, який приймає диспетчер як параметр.
Спочатку давайте зрозуміємо, що таке диспетчер корутин. Диспетчер – це механізм, який визначає, на якому потоці або потоках виконується корутинна робота. Kotlin надає кілька вбудованих диспетчерів, таких як Dispatchers.Main, Dispatchers.IO, Dispatchers.Default та інші. Ви також можете створювати власні диспетчери за допомогою класу CoroutineDispatcher.
У багатьох випадках ви використовуватимете деякий диспетчер у вашому коді, і у нього буде важливе значення під час тестування. Наприклад, ви можете мати клас, який приймає диспетчер як параметр у конструкторі та використовує його для виконання асинхронних операцій:
|
1 2 3 4 5 6 7 8 |
class SomeClass( private val dispatcher: CoroutineDispatcher, private val someRepository: SomeRepository ) { suspend fun fetchData(): SomeData = withContext(dispatcher) { someRepository.fetchData() } } |
Тепер, якщо ви хочете написати юніт-тест для цього класу, де ви хочете перевірити, що fetchData() викликається з відповідним диспетчером, вам потрібно буде знати, який саме диспетчер використовується у вашому тесті.
Давайте розглянемо практичний приклад. Уявімо, що у нас є клас Test, який приймає диспетчер як параметр та використовує його для виконання асинхронних операцій:
|
1 2 3 4 5 6 7 8 9 10 |
class Test( private val dispatcher: CoroutineDispatcher, private val foo: Foo // Just another arg ) { suspend fun testMethod(): Int = withContext(dispatcher) { // Some asynchronous operations foo.doSomething() return@withContext 42 } } |
Для того щоб протестувати цей клас, ми можемо передати власний диспетчер під час створення екземпляру класу Test у нашому тесті:
|
1 2 3 4 5 |
val mainTestDispatcher = TestCoroutineDispatcher() val testSubject = Test( dispatcher = mainTestDispatcher, foo = mockk() // Using MockK for mocks ) |
Тепер ми хочемо переконатися, що метод doSomething() викликається з відповідним диспетчером. Для цього ми можемо використати функцію coAnswers з бібліотеки MockK, яка дозволяє нам визначити поведінку мока в асинхронному контексті. У нашому тесті ми перевіряємо, що контекст відповідності диспетчера дорівнює тому, який ми передали під час створення класу:
|
1 2 3 4 5 6 7 8 9 10 |
@Test fun test() { every { foo.doSomething() } coAnswers { val dispatcher = coroutineContext[ContinuationInterceptor]!! dispatcher shouldBe mainTestDispatcher } runBlocking { testSubject.testMethod() } } |
У цьому тесті ми використовуємо функцію every з MockK для визначення поведінки мока, який відповідає на виклик doSomething()