Interoperacyjność

Compose integruje się z popularnymi frameworkami testowymi.

Interoperacyjność z Espresso

W aplikacji hybrydowej komponenty Compose można znaleźć w hierarchiach widoków i widoki w elementach kompozycyjnych Compose (za pomocą elementu kompozycyjnego AndroidView).

Nie trzeba wykonywać żadnych specjalnych czynności, aby dopasować oba typy. Widoki dopasowujesz za pomocą funkcji onView Espresso, a elementy Compose za pomocą funkcji 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()))
}

Dodawanie semantyki w zakresie widoku na potrzeby testowania interoperacyjności Compose

Ograniczanie wyszukiwań w Compose do określonych widoków

Podczas migracji złożonych interfejsów użytkownika do Compose możesz napotkać identyczne elementy Compose zagnieżdżone w wielu tradycyjnych widokach Androida, np. w RecyclerView lub ViewPager. W takich przypadkach standardowe wyszukiwanie w Compose np. onNodeWithText("Save") może się nie powieść i zwrócić błąd „Znaleziono wiele węzłów”.

Zamiast modyfikować kod produkcyjny, aby wstrzykiwać dynamiczne tagi testowe, które pozwolą odróżnić te elementy, możesz ograniczyć test Compose bezpośrednio do określonego widoku Androida.

Użyj interfejsu API onRootWithViewInteraction w regule testu. Ta funkcja akceptuje ViewInteraction Espresso, co pozwala używać Espresso do izolowania określonego widoku kontenera i wykonywania interakcji Compose wyłącznie w tej ograniczonej hierarchii.

Interakcja z elementem listy

Jeśli musisz wejść w interakcję z elementem Compose w określonym wierszu RecyclerView, użyj Espresso, aby znaleźć ten wiersz, a następnie ogranicz do niego interakcję Compose. Spowoduje to zignorowanie identycznych elementów Compose we wszystkich innych wierszach.

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

Rozwiązywanie niejednoznaczności w ViewPagerach

Gdy w pamięci znajdują się jednocześnie fragmenty z identycznymi układami Compose, możesz ograniczyć wyszukiwanie do identyfikatora widoku głównego określonego fragmentu, aby zapobiec niejednoznaczności.

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

Interoperacyjność z UiAutomator

Domyślnie elementy kompozycyjne są dostępne w UiAutomator tylko za pomocą wygodnych deskryptorów (wyświetlany tekst, opis treści itp.). Jeśli chcesz uzyskać dostęp do dowolnego elementu kompozycyjnego, który używa Modifier.testTag, musisz włączyć właściwość semantyczną testTagsAsResourceId dla poddrzewa danego elementu kompozycyjnego. Włączenie tego działania jest przydatne w przypadku elementów kompozycyjnych, które nie mają innego unikalnego uchwytu, np. elementów kompozycyjnych z możliwością przewijania (np. LazyColumn).

Włącz właściwość semantyczną tylko raz, wysoko w hierarchii elementów kompozycyjnych, aby zapewnić dostęp do wszystkich zagnieżdżonych elementów kompozycyjnych z Modifier.testTag z poziomu 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
    }
}

Każdy element kompozycyjny z Modifier.testTag(tag) może być dostępny za pomocą By.res(resourceName), używając tego samego tag co resourceName.

val device = UiDevice.getInstance(getInstrumentation())

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

Dodatkowe materiały