Menguji alur Kotlin di Android

Cara Anda menguji unit atau modul yang berkomunikasi dengan alur bergantung pada apakah subjek yang sedang diuji tersebut menggunakan alur sebagai input atau output.

  • Jika subjek yang sedang diuji mengamati alur, Anda dapat membuat alur dalam dependensi palsu yang dapat Anda kontrol dari pengujian.
  • Jika unit atau modul menampilkan alur, Anda dapat membaca dan memverifikasi satu atau beberapa item yang dimunculkan oleh alur dalam pengujian.

Membuat produser palsu

Jika subjek yang sedang diuji adalah konsumen alur, satu cara umum untuk mengujinya adalah dengan mengganti produser dengan implementasi palsu. Misalnya, dengan mempertimbangkan class yang mengamati repositori yang mengambil data dari dua sumber data dalam produksi:

subjek yang sedang diuji dan lapisan data
Gambar 1. Subjek yang sedang diuji dan lapisan data.

Untuk membuat pengujian menjadi deterministik, Anda dapat mengganti repositori dan dependensinya dengan repositori palsu yang selalu memunculkan data palsu yang sama:

dependensi diganti dengan implementasi palsu
Gambar 2. Dependensi diganti dengan implementasi palsu.

Untuk memunculkan rangkaian nilai yang telah ditentukan dalam alur, gunakan builder flow:

class MyFakeRepository : MyRepository {
    fun observeCount() = flow {
        emit(ITEM_1)
    }
}

Dalam pengujian, repositori palsu ini dimasukkan menggantikan implementasi yang sebenarnya:

@Test
fun myTest() {
    // Given a class with fake dependencies:
    val sut = MyUnitUnderTest(MyFakeRepository())
    // Trigger and verify
    ...
}

Setelah memiliki kontrol atas output subjek yang sedang diuji, Anda dapat memverifikasi bahwa kontrol tersebut berfungsi dengan benar dengan memeriksa outputnya.

Membuat pernyataan emisi alur dalam pengujian

Jika subjek yang sedang diuji memperlihatkan alur, pengujian harus membuat pernyataan tentang elemen aliran data.

Asumsikan bahwa repositori contoh sebelumnya memperlihatkan alur:

repositori dengan dependensi palsu yang menampilkan alur
Gambar 3. Repositori (subjek yang sedang diuji) dengan dependensi palsu yang menampilkan alur.

Bergantung pada kebutuhan pengujian, Anda biasanya akan memeriksa emisi pertama atau jumlah item terbatas yang berasal dari alur.

Anda dapat memakai emisi pertama ke alur dengan memanggil first(). Fungsi ini akan menunggu hingga item pertama diterima, lalu mengirim sinyal pembatalan ke produser.

@Test
fun myRepositoryTest() = runBlocking {
    // Given a repository that combines values from two data sources:
    val repository = MyRepository(fakeSource1, fakeSource2)

    // When the repository emits a value
    val firstItem = repository.counter.first() // Returns the first item in the flow

    // Then check it's the expected item
    assertThat(firstItem, isEqualTo(ITEM_1) // Using AssertJ
}

Jika pengujian perlu memeriksa beberapa nilai, pemanggilan toList() akan menyebabkan alur menunggu sumber memunculkan semua nilainya, lalu mengembalikan nilai tersebut sebagai daftar. Perlu diperhatikan bahwa ini hanya berfungsi untuk aliran data terbatas.

@Test
fun myRepositoryTest() = runBlocking {
    // Given a repository with a fake data source that emits ALL_MESSAGES
    val messages = repository.observeChatMessages().toList()

    // When all messages are emitted then they should be ALL_MESSAGES
    assertThat(messages, isEqualTo(ALL_MESSAGES))
}

Untuk aliran data yang memerlukan pengumpulan item yang lebih rumit atau yang tidak mengembalikan jumlah item terbatas, Anda dapat menggunakan Flow API untuk memilih dan mengubah item. Berikut beberapa contohnya:

// Take the second item
outputFlow.drop(1).first()

// Take the first 5 items
outputFlow.take(5).toList()

// Take the first 5 distinct items
outputFlow.take(5).toSet()

// Take the first 2 items matching a predicate
outputFlow.takeWhile(predicate).take(2).toList()

// Take the first item that matches the predicate
outputFlow.firstWhile(predicate)

// Take 5 items and apply a transformation to each
outputFlow.map(transformation).take(5)

// Takes the first item verifying that the flow is closed after that
outputFlow.single()

// Finite data streams
// Verify that the flow emits exactly N elements (optional predicate)
outputFlow.count()
outputFlow.count(predicate)

CoroutineDispatcher sebagai dependensi

Jika subjek yang sedang diuji menggunakan CoroutineDispatcher sebagai dependensi, teruskan instance TestCoroutineDispatcher dari library kotlinx-coroutines-test, lalu jalankan isi pengujian dalam metode runBlockingTest dispatcher tersebut:

private val coroutineDispatcher = TestCoroutineDispatcher()
private val uut = MyUnitUnderTest(coroutineDispatcher)

@Test
fun myTest() = coroutineDispatcher.runBlockingTest {
    // Test body
}

Referensi alur lainnya