Testumgebung mit den Test-APIs der Version 2 konfigurieren

Die Version 2 der Compose-Test-APIs (createComposeRule, createAndroidComposeRule, runComposeUiTest, runAndroidComposeUiTest usw.) ist jetzt verfügbar, um die Steuerung der Coroutine-Ausführung zu verbessern. Bei diesem Update wird nicht die gesamte API-Oberfläche dupliziert. Es wurden nur die APIs aktualisiert, die die Testumgebung einrichten.

Die v1-APIs sind veraltet. Es wird dringend empfohlen, zu den neuen APIs zu migrieren. Durch die Migration wird sichergestellt, dass Ihre Tests dem Standardverhalten von Coroutinen entsprechen und zukünftige Kompatibilitätsprobleme vermieden werden. Eine Liste der verworfenen v1-APIs finden Sie unter API-Zuweisungen.

Während die v1 APIs auf UnconfinedTestDispatcher angewiesen waren, verwenden die v2 APIs standardmäßig StandardTestDispatcher für die laufende Komposition. Durch diese Änderung wird das Verhalten von Compose-Tests an die Standard-runTest-APIs angepasst und die Ausführungsreihenfolge von Coroutinen kann explizit gesteuert werden.

Testumgebung konfigurieren

Compose-Test-APIs der Version 2 verwenden ComposeUiTestConfig, um die Testumgebung anzupassen. APIs, mit denen Einrichtungsfunktionen für Tests erstellt werden, z. B. createComposeRule, runComposeUiTest und andere zugehörige APIs, akzeptieren ComposeUiTestConfig. Dieses Konfigurationsobjekt fasst umweltbezogene APIs wie effectContext, runTestContext und testTimeout in einem einzigen Objekt zusammen.

Das Konfigurationsmodell verwaltet auch inputMode. Die Compose Test V2-APIs erzwingen standardmäßig InputMode.Touch zu Beginn jedes Tests, um Determinismus zu gewährleisten und zu verhindern, dass der Eingabemodusstatus zwischen Tests weitergegeben wird.

ComposeUiTestConfig ist Teil der Compose-Test-APIs v2, die standardmäßig StandardTestDispatcher verwenden. Wenn in Ihren Tests V1-APIs verwendet werden, lesen Sie den Abschnitt Zu V2-Test-APIs migrieren, bevor Sie ComposeUiTestConfig übernehmen.

Migrieren Sie zu ComposeUiTestConfig

Bei den Überladungen zum Erstellen von Einrichtungsfunktionen in Tests sind mehrere Überladungen, die einzelne Konfigurationsparameter wie effectContext, runTestContext oder testTimeout akzeptieren, veraltet. Aktualisieren Sie Ihre Tests, damit sie stattdessen ComposeUiTestConfig verwenden, wie im folgenden Beispiel gezeigt:

Standardmäßiger Eingabemodus

Tests können während der Migration fehlschlagen, wenn sie auf Eingabemodi ohne Touch-Funktion angewiesen sind, die vor dem Start des Tests über Instrumentierungs-APIs konfiguriert wurden. In den Einrichtungsfunktionen für Tests erzwingt das System standardmäßig InputMode.Touch zu Beginn jedes Tests, um mehr Determinismus zu erreichen und Statuslecks zu verhindern. Dabei werden der Umgebungsgerätestatus und die Einrichtung vor dem Test überschrieben.

Geben Sie den erforderlichen Eingabemodus in ComposeUiTestConfig an, um dieses Problem zu beheben:

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

    @Test
    fun testFocus() {}
}

Wenn Sie den Eingabemodus für einzelne Testläufe anstelle der gesamten Testklasse konfigurieren möchten, übergeben Sie ComposeUiTestConfig an 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
    }
}

Informationen zu anderen Migrationsproblemen und Lösungen finden Sie unter Häufige Fehler und ihre Behebung.

Zu v2-Test-APIs migrieren

Beim Upgrade auf die APIs der Version 2 können Sie in der Regel Suchen + Ersetzen verwenden, um die Paketimporte zu aktualisieren und die neuen Dispatcher-Änderungen zu übernehmen.

Alternativ können Sie Gemini mit dem folgenden Prompt auffordern, eine Migration zu Version 2 der Compose-Test-APIs durchzuführen:

Von v1-Test-APIs zu v2-Test-APIs migrieren

In diesem Prompt wird dieser Leitfaden verwendet, um zur v2-Test-API zu migrieren.

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

KI-Prompts verwenden

KI-Prompts sind für die Verwendung in Gemini in Android Studio vorgesehen.

Weitere Informationen zu Gemini in Studio finden Sie unter https://developer.android.com/studio/gemini/overview.

In der folgenden Tabelle finden Sie die entsprechenden V2-APIs für die eingestellten V1-APIs:

Eingestellt (Version 1)

Ersatz (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

Abwärtskompatibilität und Ausnahmen

Die vorhandenen v1-APIs sind jetzt veraltet, verwenden aber weiterhin UnconfinedTestDispatcher, um das vorhandene Verhalten beizubehalten und Breaking Changes zu verhindern.

Die einzige Ausnahme, bei der sich das Standardverhalten geändert hat, ist folgende:

Der Standard-Test-Dispatcher, der zum Ausführen der Komposition in der Klasse AndroidComposeUiTestEnvironment verwendet wird, wurde von UnconfinedTestDispatcher zu StandardTestDispatcher geändert. Dies betrifft Fälle, in denen Sie eine Instanz mit dem Konstruktor erstellen oder eine Unterklasse von AndroidComposeUiTestEnvironment erstellen und diesen Konstruktor aufrufen.

Wichtige Änderung: Auswirkungen auf die Ausführung von Coroutinen

Der Hauptunterschied zwischen Version 1 und Version 2 der APIs besteht darin, wie Coroutinen verteilt werden:

  • v1 APIs (UnconfinedTestDispatcher): Wenn eine Coroutine gestartet wurde, wurde sie sofort im aktuellen Thread ausgeführt und war oft abgeschlossen, bevor die nächste Zeile des Testcodes ausgeführt wurde. Im Gegensatz zum Produktionsverhalten kann diese sofortige Ausführung versehentlich echte Timing-Probleme oder Race Conditions maskieren, die in einer Live-Anwendung auftreten würden.
  • v2-APIs (StandardTestDispatcher): Wenn eine Coroutine gestartet wird, wird sie in die Warteschlange gestellt und erst ausgeführt, wenn die virtuelle Uhr im Test explizit weitergestellt wird. Standard-Compose-Test-APIs (z. B. waitForIdle()) übernehmen diese Synchronisierung bereits. Die meisten Tests, die auf diesen Standard-APIs basieren, sollten daher ohne Änderungen funktionieren.

Häufige Fehler und ihre Behebung

Wenn Ihre Tests nach dem Upgrade auf Version 2 fehlschlagen, weisen sie wahrscheinlich das folgende Muster auf:

  • Fehler: Sie starten eine Aufgabe (z. B. lädt ein ViewModel Daten), aber die Assertion schlägt sofort fehl, weil sich die Daten noch im Status „Wird geladen“ befinden.
  • Ursache: Bei den V2-APIs werden Coroutinen in die Warteschlange gestellt, anstatt sofort ausgeführt zu werden. Die Aufgabe wurde in die Warteschlange gestellt, aber nie ausgeführt, bevor das Ergebnis geprüft wurde.
  • Korrektur: Zeit explizit vorverlegen. Sie müssen dem v2-Dispatcher explizit mitteilen, wann er Aufgaben ausführen soll.

Bisherige Vorgehensweise

In Version 1 wurde die Aufgabe sofort gestartet und beendet. In Version 2 schlägt der folgende Code fehl, weil loadData() noch nicht ausgeführt wurde.

// 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)

Verwenden Sie waitForIdle oder runOnIdle, um in die Warteschlange gestellte Aufgaben vor dem Assert auszuführen.

Option 1: Wenn Sie waitForIdle verwenden, wird die Zeit bis zum Leerlauf der Benutzeroberfläche vorgerückt. So wird überprüft, ob die Coroutine ausgeführt wurde.

viewModel.loadData()

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

assertEquals(Success, viewModel.state.value)

Option 2: Bei Verwendung von runOnIdle wird der Codeblock im UI-Thread ausgeführt, nachdem die UI inaktiv geworden ist.

viewModel.loadData()

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

Manuelle Synchronisierung

In Szenarien mit manueller Synchronisierung, z. B. wenn das automatische Vorrücken deaktiviert ist, führt das Starten einer Coroutine nicht zur sofortigen Ausführung, da die Testuhr angehalten wird. Wenn Sie Coroutinen in der Warteschlange ausführen möchten, ohne die virtuelle Uhr vorzustellen, verwenden Sie die runCurrent() API. Dadurch werden Tasks ausgeführt, die für die aktuelle virtuelle Zeit geplant sind.

composeTestRule.mainClock.scheduler.runCurrent()

Im Gegensatz zu waitForIdle(), bei dem die Testuhr so lange voranschreitet, bis die Benutzeroberfläche stabilisiert ist, werden bei runCurrent() ausstehende Aufgaben ausgeführt, während die aktuelle virtuelle Zeit beibehalten wird. So können Zwischenzustände überprüft werden, die andernfalls übersprungen würden, wenn die Zeit auf einen Leerlaufzustand vorgerückt würde.

Der zugrunde liegende Test-Scheduler, der in der Testumgebung verwendet wird, wird verfügbar gemacht. Dieser Scheduler kann in Verbindung mit der Kotlin-API runTest verwendet werden, um die Testuhr zu synchronisieren.

Migrieren Sie zu runComposeUiTest

Wenn Sie Compose-Test-APIs zusammen mit der Kotlin-runTest-API verwenden, wird dringend empfohlen, zu runComposeUiTest zu wechseln.

Bisherige Vorgehensweise

Wenn Sie createComposeRule in Verbindung mit runTest verwenden, werden zwei separate Zeitgeber erstellt: einer für Compose und einer für den Test-Coroutine-Scope. Diese Konfiguration kann dazu führen, dass Sie den Test-Scheduler manuell synchronisieren müssen.

@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()
    }
}

Die runComposeUiTest API führt Ihren Testblock automatisch in ihrem eigenen runTest-Bereich aus. Die Testuhr wird mit der Compose-Umgebung synchronisiert, sodass Sie den Scheduler nicht mehr manuell verwalten müssen.

    @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()
    }
}