Les versions v2 des API de test Compose (createComposeRule, createAndroidComposeRule, runComposeUiTest, runAndroidComposeUiTest, etc.) sont désormais disponibles pour améliorer le contrôle de l'exécution des coroutines. Cette mise à jour ne duplique pas l'intégralité de la surface de l'API. Seules les API qui établissent l'environnement de test ont été mises à jour.
Les API v1 sont obsolètes. Nous vous recommandons vivement de migrer vers les nouvelles API. La migration permet de vérifier que vos tests sont conformes au comportement standard des coroutines et d'éviter les futurs problèmes de compatibilité. Pour obtenir la liste des API v1 obsolètes, consultez Mappages d'API.
Ces modifications sont incluses dans androidx.compose.ui:ui-test-junit4:1.11.0-alpha03+ et androidx.compose.ui:ui-test:1.11.0-alpha03+.
Alors que les API v1 s'appuyaient sur UnconfinedTestDispatcher, les API v2 utilisent StandardTestDispatcher par défaut pour la composition en cours d'exécution. Cette modification aligne le comportement des tests Compose sur les API runTest standards et permet de contrôler explicitement l'ordre d'exécution des coroutines.
Configurer l'environnement de test
Les API Compose Test v2 utilisent ComposeUiTestConfig pour personnaliser l'environnement de test. Les API qui créent des fonctions de configuration pour les tests, telles que createComposeRule, runComposeUiTest et d'autres API associées, acceptent ComposeUiTestConfig. Cet objet de configuration regroupe les API liées à l'environnement, comme effectContext, runTestContext et testTimeout, dans un seul objet.
Le modèle de configuration gère également inputMode. Les API Compose Test v2 appliquent InputMode.Touch par défaut au début de chaque test pour assurer le déterminisme et empêcher l'état du mode de saisie de fuiter entre les tests.
ComposeUiTestConfig fait partie des API Compose Test v2, qui utilisent StandardTestDispatcher par défaut. Si vos tests utilisent des API v1, consultez Migrer vers les API de test v2 avant d'adopter ComposeUiTestConfig.
Migrer vers ComposeUiTestConfig
Plusieurs surcharges qui acceptent des paramètres de configuration individuels (tels que effectContext, runTestContext ou testTimeout) sont obsolètes dans les surcharges permettant de créer des fonctions de configuration dans les tests. Mettez à jour vos tests pour utiliser ComposeUiTestConfig à la place, comme indiqué dans l'exemple suivant :
val testConfig = ComposeUiTestConfig( effectContext = EmptyCoroutineContext, runTestContext = EmptyCoroutineContext, testTimeout = 30.seconds ) @get:Rule val rule = createComposeRule(config = testConfig) // OR runComposeUiTest(config = testConfig) {}
Mode de saisie par défaut
Les tests peuvent échouer lors de la migration s'ils s'appuient sur des modes de saisie non tactiles configurés via des API d'instrumentation avant le début du test. Dans les fonctions de configuration des tests, le système applique InputMode.Touch par défaut au début de chaque test pour plus de déterminisme et pour éviter les fuites d'état, en remplaçant l'état ambiant de l'appareil et la configuration avant le test.
Pour résoudre ce problème, spécifiez le mode d'entrée requis dans ComposeUiTestConfig :
class FocusTest { @get:Rule val rule = createComposeRule( config = ComposeUiTestConfig(inputMode = InputMode.Keyboard) ) @Test fun testFocus() {} }
Pour configurer le mode de saisie pour des cas de test individuels plutôt que pour l'ensemble de la classe de test, transmettez ComposeUiTestConfig à 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 } }
Pour d'autres problèmes de migration et leurs solutions, consultez Cas de non-respect courants et solutions possibles.
Migrer vers les API de test v2
Lorsque vous passez aux API v2, vous pouvez généralement utiliser Rechercher et remplacer pour mettre à jour les importations de packages et adopter les nouvelles modifications du répartiteur.
Vous pouvez également demander à Gemini d'effectuer une migration vers la version 2 des API de test Compose à l'aide du prompt suivant :
Prompt d'IA
Migrer des API de test v1 vers les API de test v2
Cette invite utilisera ce guide pour migrer vers les API de test v2.
Migrate to Compose testing v2 APIs using the official
migration guide.Utilisez le tableau suivant pour faire correspondre les API v1 obsolètes à leurs remplacements v2 :
Obsolète (v1) |
Remplacement (v2) |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rétrocompatibilité et exceptions
Les API v1 existantes sont désormais obsolètes, mais vous pouvez continuer à utiliser UnconfinedTestDispatcher pour conserver le comportement existant et éviter les modifications destructives.
La seule exception où le comportement par défaut a changé est la suivante :
Le répartiteur de test par défaut utilisé pour exécuter la composition dans la classe AndroidComposeUiTestEnvironment est passé de UnconfinedTestDispatcher à StandardTestDispatcher. Cela affecte les cas où vous créez une instance à l'aide du constructeur ou de la sous-classe AndroidComposeUiTestEnvironment, et appelez ce constructeur.
Modification clé : impact sur l'exécution des coroutines
La principale différence entre les versions 1 et 2 des API réside dans la manière dont les coroutines sont distribuées :
- API v1 (
UnconfinedTestDispatcher) : lorsqu'une coroutine était lancée, elle s'exécutait immédiatement sur le thread actuel, se terminant souvent avant l'exécution de la ligne de code de test suivante. Contrairement au comportement en production, cette exécution immédiate peut masquer par inadvertance de réels problèmes de timing ou des conditions de course qui se produiraient dans une application en direct. - API v2 (
StandardTestDispatcher) : lorsqu'une coroutine est lancée, elle est mise en file d'attente et ne s'exécute pas tant que le test n'avance pas explicitement l'horloge virtuelle. Les API de test Compose standards (telles quewaitForIdle()) gèrent déjà cette synchronisation. La plupart des tests reposant sur ces API standards devraient donc continuer à fonctionner sans aucune modification.
Cas de non-respect courants et solutions possibles
Si vos tests échouent après la mise à niveau vers la version 2, ils présentent probablement le schéma suivant :
- Échec : vous lancez une tâche (par exemple, un ViewModel charge des données), mais votre assertion échoue immédiatement, car les données sont toujours dans un état "Loading" (Chargement).
- Cause : Avec les API v2, les coroutines sont mises en file d'attente plutôt qu'exécutées immédiatement. La tâche a été mise en file d'attente, mais n'a jamais été exécutée avant la vérification du résultat.
- Correction : avancez explicitement dans le temps. Vous devez indiquer explicitement au répartiteur v2 quand exécuter le travail.
Approche précédente
Dans la version 1, la tâche se lançait et se terminait immédiatement. Dans la version 2, le code suivant échoue, car loadData() n'a pas encore été exécuté.
// 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)
Approche recommandée
Utilisez waitForIdle ou runOnIdle pour exécuter les tâches en file d'attente avant l'assertion.
Option 1 : L'utilisation de waitForIdle fait avancer l'horloge jusqu'à ce que l'UI soit inactive, ce qui permet de vérifier que la coroutine s'est exécutée.
viewModel.loadData()
// Explicitly run all queued tasks
composeTestRule.waitForIdle()
assertEquals(Success, viewModel.state.value)
Option 2 : L'utilisation de runOnIdle exécute le bloc de code sur le thread UI une fois que l'UI est devenue inactive.
viewModel.loadData()
// Run the assertion after the UI is idle
composeTestRule.runOnIdle {
assertEquals(Success, viewModel.state.value)
}
Synchronisation manuelle
Dans les scénarios impliquant une synchronisation manuelle, par exemple lorsque l'avance automatique est désactivée, le lancement d'une coroutine n'entraîne pas une exécution immédiate, car l'horloge de test est suspendue. Pour exécuter des coroutines dans la file d'attente sans faire avancer l'horloge virtuelle, utilisez l'API runCurrent(). Cette méthode exécute les tâches planifiées pour l'heure virtuelle actuelle.
composeTestRule.mainClock.scheduler.runCurrent()
Contrairement à waitForIdle(), qui fait avancer l'horloge de test jusqu'à ce que l'UI se stabilise, runCurrent() exécute les tâches en attente tout en conservant l'heure virtuelle actuelle. Ce comportement permet de vérifier les états intermédiaires qui seraient ignorés si l'horloge était avancée à un état inactif.
Le planificateur de test sous-jacent utilisé dans l'environnement de test est exposé. Ce planificateur peut être utilisé conjointement avec l'API Kotlin runTest pour synchroniser l'horloge de test.
Migrer vers runComposeUiTest
Si vous utilisez les API de test Compose en même temps que l'API Kotlin runTest, nous vous recommandons vivement de passer à runComposeUiTest.
Approche précédente
L'utilisation de createComposeRule en association avec runTest crée deux horloges distinctes : une pour Compose et une pour le champ d'application de la coroutine de test. Cette configuration peut vous obliger à synchroniser manuellement le planificateur de tests.
@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() } }
Approche recommandée
L'API runComposeUiTest exécute automatiquement votre bloc de test dans sa propre portée runTest. L'horloge de test est synchronisée avec l'environnement Compose. Vous n'avez donc plus besoin de gérer le planificateur manuellement.
@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() } }