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, sondern nur die APIs, die die Testumgebung einrichten.
Die v1-APIs sind veraltet. Wir empfehlen dringend, zu den neuen APIs zu migrieren. Durch die Migration wird sichergestellt, dass Ihre Tests dem Standardverhalten von Coroutinen entsprechen, und zukünftige Kompatibilitätsprobleme werden vermieden. Eine Liste der verworfenen v1-APIs finden Sie unter API-Zuweisungen.
Diese Änderungen sind in androidx.compose.ui:ui-test-junit4:1.11.0-alpha03+ und androidx.compose.ui:ui-test:1.11.0-alpha03+ enthalten.
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
Die Compose Test v2-APIs 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. Bei Compose Test v2-APIs wird InputMode.Touch standardmäßig zu Beginn jedes Tests erzwungen, um Determinismus zu gewährleisten und zu verhindern, dass der Status des Eingabemodus zwischen Tests übertragen wird.
ComposeUiTestConfig ist Teil der Compose Test v2-APIs, 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
Innerhalb der Ü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 stattdessen ComposeUiTestConfig verwendet wird, wie im folgenden Beispiel gezeigt:
val testConfig = ComposeUiTestConfig( effectContext = EmptyCoroutineContext, runTestContext = EmptyCoroutineContext, testTimeout = 30.seconds ) @get:Rule val rule = createComposeRule(config = testConfig) // OR runComposeUiTest(config = testConfig) {}
Standardeingabemodus
Tests können während der Migration fehlschlagen, wenn sie auf Eingabemodi ohne Touch-Funktion basieren, die vor dem Test ü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 das 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 deren Behebung finden Sie unter Häufige Fehler und ihre Behebung.
Zu den 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 Änderungen am Dispatcher zu übernehmen.
Alternativ können Sie Gemini mit dem folgenden Prompt bitten, eine Migration zu Version 2 der Compose-Test-APIs durchzuführen:
KI-Prompt
Von v1-Test-APIs zu v2-Test-APIs migrieren
In diesem Prompt wird beschrieben, wie Sie zu den Test-APIs der Version 2 migrieren.
Migrate to Compose testing v2 APIs using the official
migration guide.In der folgenden Tabelle sehen Sie, wie die eingestellten V1-APIs den V2-APIs zugeordnet werden:
Verworfen (Version 1) |
Ersatz (v2) |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Abwärtskompatibilität und Ausnahmen
Die vorhandenen v1-APIs sind jetzt veraltet, verwenden aber weiterhin UnconfinedTestDispatcher, um das vorhandene Verhalten beizubehalten und funktionsgefährdende Änderungen 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 weiterhin 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 APIs der Version 2 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: Die Zeit wird explizit vorverlegt. 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)
Empfohlene Vorgehensweise
Verwenden Sie waitForIdle oder runOnIdle, um in die Warteschlange gestellte Aufgaben vor dem Assert auszuführen.
Option 1: Wenn Sie waitForIdle verwenden, wird die Uhr 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 voranzustellen, verwenden Sie die runCurrent() API. Dadurch werden Aufgaben 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. Dieses Verhalten ermöglicht die Überprüfung von Zwischenzuständen, 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. Bei dieser Konfiguration müssen Sie den Test-Scheduler möglicherweise manuell synchronisieren.
@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() } }
Empfohlene Vorgehensweise
Die runComposeUiTest API führt Ihren Testblock automatisch im 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() } }