API pengujian Compose v2

API pengujian Compose versi 2 (createComposeRule, createAndroidComposeRule, runComposeUiTest, runAndroidComposeUiTest, dll.) kini tersedia untuk meningkatkan kontrol atas eksekusi coroutine. Pembaruan ini tidak menduplikasi seluruh platform API; hanya API yang membuat lingkungan pengujian yang telah diperbarui.

API v1 sudah tidak digunakan lagi, dan sangat direkomendasikan untuk bermigrasi ke API baru. Migrasi memverifikasi bahwa pengujian Anda selaras dengan perilaku rutin standar dan menghindari masalah kompatibilitas pada masa mendatang. Untuk mengetahui daftar API v1 yang tidak digunakan lagi, lihat pemetaan API.

Perubahan ini disertakan dalam androidx.compose.ui:ui-test-junit4:1.11.0-alpha03+ dan androidx.compose.ui:ui-test:1.11.0-alpha03+.

Meskipun API v1 mengandalkan UnconfinedTestDispatcher, API v2 menggunakan StandardTestDispatcher secara default untuk komposisi yang sedang berjalan. Perubahan ini menyelaraskan perilaku pengujian Compose dengan API runTest standar dan memberikan kontrol eksplisit atas urutan eksekusi coroutine.

Mengonfigurasi lingkungan pengujian

API pengujian Compose v2 menggunakan ComposeUiTestConfig untuk menyesuaikan lingkungan pengujian. API yang membuat fungsi penyiapan untuk pengujian, seperti createComposeRule, runComposeUiTest, dan API terkait lainnya, menerima ComposeUiTestConfig. Objek konfigurasi ini menggabungkan API terkait lingkungan seperti effectContext, runTestContext, dan testTimeout menjadi satu objek.

Model konfigurasi juga mengelola inputMode. API pengujian Compose v2 menerapkan InputMode.Touch secara default di awal setiap pengujian untuk memastikan deterministik dan mencegah kebocoran status mode input di antara pengujian.

ComposeUiTestConfig adalah bagian dari API Compose Test v2, yang menggunakan StandardTestDispatcher secara default. Jika pengujian Anda menggunakan API v1, lihat Bermigrasi ke API pengujian v2 sebelum menggunakan ComposeUiTestConfig.

Bermigrasi ke ComposeUiTestConfig

Dalam overload untuk membuat fungsi penyiapan dalam pengujian, beberapa overload yang menerima parameter konfigurasi individual -- seperti effectContext, runTestContext, atau testTimeout -- tidak digunakan lagi. Perbarui pengujian Anda untuk menggunakan ComposeUiTestConfig, seperti yang ditunjukkan dalam contoh berikut:

Mode input default

Pengujian dapat gagal selama migrasi jika mengandalkan mode input non-sentuh yang dikonfigurasi melalui API instrumentasi sebelum pengujian dimulai. Dalam fungsi penyiapan untuk pengujian, sistem menerapkan InputMode.Touch secara default di awal setiap pengujian untuk lebih deterministik dan mencegah kebocoran status, menggantikan status perangkat sekitar dan penyiapan pra-pengujian.

Untuk mengatasinya, tentukan mode input yang diperlukan di ComposeUiTestConfig:

class FocusTest {
    @get:Rule
    val rule = createComposeRule(
        config = ComposeUiTestConfig(inputMode = InputMode.Keyboard)
    )

    @Test
    fun testFocus() {}
}

Untuk mengonfigurasi mode input untuk setiap kasus pengujian, bukan seluruh class pengujian, teruskan ComposeUiTestConfig ke runComposeUiTest:

class FocusTest {
    @Test
    fun testTouchMode() = runComposeUiTest {
        // Runs with the default InputMode.Touch
    }

    @Test
    fun testKeyboardMode() = runComposeUiTest(
        ComposeUiTestConfig(inputMode = InputMode.Keyboard)
    ) {
        // Runs with InputMode.Keyboard
    }
}

Untuk masalah dan resolusi migrasi lainnya, lihat Kegagalan umum dan cara memperbaikinya.

Bermigrasi ke API pengujian v2

Saat mengupgrade ke API v2, Anda umumnya dapat menggunakan Find + Replace untuk memperbarui impor paket dan menerapkan perubahan dispatcher baru.

Atau, minta Gemini melakukan migrasi ke API pengujian Compose v2 dengan perintah berikut:

Bermigrasi dari API pengujian v1 ke API pengujian v2

Perintah ini akan menggunakan panduan ini untuk bermigrasi ke API pengujian v2.

Migrate to Compose testing v2 APIs using the official
migration guide.

Menggunakan perintah AI

Perintah AI dimaksudkan untuk digunakan dalam Gemini di Android Studio.

Pelajari lebih lanjut Gemini di Studio di sini: https://developer.android.com/studio/gemini/overview

Gunakan tabel berikut untuk memetakan API v1 yang tidak digunakan lagi ke penggantinya di v2:

Tidak digunakan lagi (v1)

Penggantian (v2)

androidx.compose.ui.test.junit4.createComposeRule

androidx.compose.ui.test.junit4.v2.createComposeRule

androidx.compose.ui.test.junit4.createAndroidComposeRule

androidx.compose.ui.test.junit4.v2.createAndroidComposeRule

androidx.compose.ui.test.junit4.createEmptyComposeRule

androidx.compose.ui.test.junit4.v2.createEmptyComposeRule

androidx.compose.ui.test.junit4.AndroidComposeTestRule

androidx.compose.ui.test.junit4.v2.AndroidComposeTestRule

androidx.compose.ui.test.runComposeUiTest

androidx.compose.ui.test.v2.runComposeUiTest

androidx.compose.ui.test.runAndroidComposeUiTest

androidx.compose.ui.test.v2.runAndroidComposeUiTest

androidx.compose.ui.test.runEmptyComposeUiTest

androidx.compose.ui.test.v2.runEmptyComposeUiTest

androidx.compose.ui.test.AndroidComposeUiTestEnvironment

androidx.compose.ui.test.v2.AndroidComposeUiTestEnvironment

Kompatibilitas mundur dan pengecualian

API v1 yang ada kini tidak digunakan lagi, tetapi terus menggunakan UnconfinedTestDispatcher untuk mempertahankan perilaku yang ada dan mencegah perubahan yang merusak.

Berikut adalah satu-satunya pengecualian di mana perilaku default telah berubah:

Dispatcher pengujian default yang digunakan untuk menjalankan komposisi di class AndroidComposeUiTestEnvironment telah beralih dari UnconfinedTestDispatcher ke StandardTestDispatcher. Hal ini memengaruhi kasus saat Anda membuat instance menggunakan konstruktor, atau membuat subclass AndroidComposeUiTestEnvironment, dan memanggil konstruktor tersebut.

Perubahan penting: Dampak pada eksekusi coroutine

Perbedaan utama antara API v1 dan v2 adalah cara coroutine dikirim:

  • API v1 (UnconfinedTestDispatcher): Saat diluncurkan, coroutine akan langsung dieksekusi di thread saat ini, dan sering kali selesai sebelum baris kode pengujian berikutnya dijalankan. Tidak seperti perilaku produksi, eksekusi langsung ini dapat secara tidak sengaja menyembunyikan masalah pengaturan waktu atau kondisi persaingan yang sebenarnya yang akan terjadi dalam aplikasi aktif.
  • API v2 (StandardTestDispatcher): Saat diluncurkan, coroutine akan diantrekan dan tidak dieksekusi hingga pengujian secara eksplisit memajukan waktu virtual. API pengujian Compose standar (seperti waitForIdle()) sudah menangani sinkronisasi ini, sehingga sebagian besar pengujian yang mengandalkan API standar ini akan terus berfungsi tanpa perubahan.

Kegagalan umum dan cara memperbaikinya

Jika pengujian Anda gagal setelah mengupgrade ke v2, kemungkinan pengujian tersebut menunjukkan pola berikut:

  • Kegagalan: Anda meluncurkan tugas (misalnya, ViewModel memuat data), tetapi pernyataan Anda langsung gagal karena data masih dalam status "Memuat".
  • Penyebab: Dengan API v2, coroutine diantrekan, bukan dieksekusi segera. Tugas dimasukkan dalam antrean, tetapi tidak pernah benar-benar dijalankan sebelum hasilnya diperiksa.
  • Perbaiki: Memajukan waktu secara eksplisit. Anda harus secara eksplisit memberi tahu dispatcher v2 kapan harus menjalankan tugas.

Pendekatan sebelumnya

Di v1, tugas diluncurkan dan selesai dengan segera. Di v2, kode berikut gagal karena loadData() belum benar-benar berjalan.

// In v1, this launched and finished immediately.
viewModel.loadData()

// In v2, this fails because loadData() hasn't actually run yet!
assertEquals(Success, viewModel.state.value)

Gunakan waitForIdle atau runOnIdle untuk mengeksekusi tugas yang diantrekan sebelum melakukan pernyataan.

Opsi 1: Menggunakan waitForIdle akan memajukan waktu hingga UI tidak ada aktivitas, memverifikasi bahwa coroutine telah berjalan.

viewModel.loadData()

// Explicitly run all queued tasks
composeTestRule.waitForIdle()

assertEquals(Success, viewModel.state.value)

Opsi 2: Menggunakan runOnIdle akan mengeksekusi blok kode di thread UI setelah UI menjadi tidak ada aktivitas.

viewModel.loadData()

// Run the assertion after the UI is idle
composeTestRule.runOnIdle {
    assertEquals(Success, viewModel.state.value)
}

Sinkronisasi manual

Dalam skenario yang melibatkan sinkronisasi manual, seperti saat maju otomatis dinonaktifkan, peluncuran coroutine tidak menghasilkan eksekusi langsung karena clock pengujian dijeda. Untuk mengeksekusi coroutine dalam antrean tanpa memajukan clock virtual, gunakan runCurrent() API. Tindakan ini menjalankan tugas yang dijadwalkan untuk waktu virtual saat ini.

composeTestRule.mainClock.scheduler.runCurrent()

Berbeda dengan waitForIdle(), yang memajukan clock pengujian hingga UI stabil, runCurrent() menjalankan tugas yang tertunda sambil mempertahankan waktu virtual saat ini. Perilaku ini memungkinkan verifikasi status perantara yang sebaliknya akan dilewati jika clock dimajukan ke status tidak ada aktivitas.

Penjadwal pengujian dasar yang digunakan di lingkungan pengujian diekspos. Penjadwal ini dapat digunakan bersama dengan runTest API Kotlin untuk menyelaraskan clock pengujian.

Bermigrasi ke runComposeUiTest

Jika Anda menggunakan API pengujian Compose bersama dengan API runTest Kotlin, sebaiknya beralih ke runComposeUiTest.

Pendekatan sebelumnya

Menggunakan createComposeRule bersama dengan runTest akan membuat dua clock terpisah: satu untuk Compose, dan satu untuk cakupan coroutine pengujian. Konfigurasi ini dapat memaksa Anda menyinkronkan penjadwal pengujian secara manual.

@get:Rule
val composeTestRule = createComposeRule()

@Test
fun testWithCoroutines() {
    composeTestRule.setContent {
        var status by remember { mutableStateOf("Loading...") }
        LaunchedEffect(Unit) {
            delay(1000)
            status = "Done!"
        }
        Text(text = status)
    }

    // NOT RECOMMENDED
    // Fails: runTest creates a new, separate scheduler.
    // Advancing time here does NOT advance the compose clock.
    // To fix this without migrating, you would need to share the scheduler
    // by passing 'composeTestRule.mainClock.scheduler' to runTest.
    runTest {
        composeTestRule.onNodeWithText("Loading...").assertIsDisplayed()
        advanceTimeBy(1000)
        composeTestRule.onNodeWithText("Done!").assertIsDisplayed()
    }
}

API runComposeUiTest akan otomatis menjalankan blok pengujian Anda dalam cakupan runTest-nya sendiri. Jam pengujian disinkronkan dengan lingkungan Compose, sehingga Anda tidak perlu lagi mengelola penjadwal secara manual.

    @Test
    fun testWithCoroutines() = runComposeUiTest {
        setContent {
            var status by remember { mutableStateOf("Loading...") }
            LaunchedEffect(Unit) {
                delay(1000)
                status = "Done!"
            }
            Text(text = status)
        }

        onNodeWithText("Loading...").assertIsDisplayed()
        mainClock.advanceTimeBy(1000 + 16 /* Frame buffer */)
        onNodeWithText("Done!").assertIsDisplayed()
    }
}