Interopérabilité

Compose s'intègre aux frameworks de test courants.

Interopérabilité avec Espresso

Dans une application hybride, vous pouvez trouver des composants Compose dans des hiérarchies de vues et des vues dans des composables Compose (via le AndroidView composable).

Aucune étape particulière n'est requise pour correspondre à chaque type. Vous faites correspondre les vues avec Espresso's onView, et les éléments Compose avec ComposeTestRule.

@Test
fun androidViewInteropTest() {
    // Check the initial state of a TextView that depends on a Compose state.
    Espresso.onView(withText("Hello Views")).check(matches(isDisplayed()))
    // Click on the Compose button that changes the state.
    composeTestRule.onNodeWithText("Click here").performClick()
    // Check the new value.
    Espresso.onView(withText("Hello Compose")).check(matches(isDisplayed()))
}

Ajouter une sémantique limitée à la vue pour les tests d'interopérabilité Compose

Limiter les recherches Compose à des vues spécifiques

Lorsque vous migrez des interfaces utilisateur complexes vers Compose, vous pouvez rencontrer des éléments Compose identiques imbriqués dans plusieurs vues Android traditionnelles, par exemple dans un RecyclerView ou un ViewPager. Dans ce cas, une recherche Compose standard comme onNodeWithText("Save") peut échouer avec une erreur "Multiple nodes found" (Plusieurs nœuds trouvés).

Au lieu de modifier votre code de production pour injecter des balises de test dynamiques afin de distinguer ces éléments, vous pouvez limiter votre test Compose directement à une vue Android spécifique.

Utilisez l'API onRootWithViewInteraction sur votre règle de test. Cette fonction accepte un ViewInteraction Espresso, ce qui vous permet d'utiliser Espresso pour isoler une vue de conteneur spécifique et effectuer des interactions Compose exclusivement dans cette hiérarchie limitée.

Interagir avec un élément de liste

Si vous devez interagir avec un élément Compose dans une ligne RecyclerView spécifique, utilisez Espresso pour localiser la ligne, puis limitez votre interaction Compose à celle-ci. Cela ignore les éléments Compose identiques dans toutes les autres lignes.

@Test
fun testComposeButtonInsideRecyclerViewItem() = runComposeUiTest {
    // Scroll to the desired position using Espresso
    Espresso.onView(withId(recyclerViewId))
        .perform(RecyclerViewActions.scrollToPosition<MyViewHolder>(3))

    // Define an Espresso ViewInteraction that uniquely identifies the row
    val rowView = Espresso.onView(
        allOf(
            withId(rootViewId),
            hasDescendant(withText("Item #3"))
        )
    )

    // Scope the Compose search strictly to that specific row View
    onRootWithViewInteraction(rowView)
        .onNode(hasText("Like"))
        .performClick()
}

Résoudre l'ambiguïté dans les ViewPager

Lorsque plusieurs fragments avec des mises en page Compose identiques sont en mémoire simultanément, vous pouvez limiter la recherche à l'ID de la vue racine du fragment spécifique pour éviter toute ambiguïté de correspondance.

@Test
fun testComposeButtonInsideViewPagerItem() = runComposeUiTest {
    // Swipe to the desired page using Espresso
    Espresso.onView(withId(viewPagerViewId)).perform(swipeLeft())

    // Identify the specific container view using Espresso
    val fragmentB = Espresso.onView(withId(fragmentRootViewId))

    // The generic text "Save" is now unique within this view scope
    onRootWithViewInteraction(fragmentB)
        .onNode(hasText("Save"))
        .assertIsDisplayed()
}

Interopérabilité avec UiAutomator

Par défaut, les composables ne sont accessibles depuis UiAutomator que par leurs descripteurs pratiques (texte affiché, description du contenu, etc.). Si vous voulez accéder à n'importe quel composable qui utilise Modifier.testTag, vous devez activer la propriété sémantique testTagsAsResourceId pour la sous-arborescence de ce composable. Ce comportement est utile pour les composables qui ne possèdent pas d'autre identifiant unique, comme les composables à faire défiler (par exemple, LazyColumn).

N'activez la propriété sémantique qu'une seule fois au sommet de la hiérarchie de vos composables afin de vous assurer que tous les composables imbriqués avec Modifier.testTag sont accessibles depuis UiAutomator.

Scaffold(
    // Enables for all composables in the hierarchy.
    modifier = Modifier.semantics {
        testTagsAsResourceId = true
    }
){
    // Modifier.testTag is accessible from UiAutomator for composables nested here.
    LazyColumn(
        modifier = Modifier.testTag("myLazyColumn")
    ){
        // Content
    }
}

Tout composable avec Modifier.testTag(tag) est accessible à l'aide de By.res(resourceName) avec le même tag que resourceName.

val device = UiDevice.getInstance(getInstrumentation())

val lazyColumn: UiObject2 = device.findObject(By.res("myLazyColumn"))
// Some interaction with the lazyColumn.

Autres ressources