Совместимость

Compose интегрируется с распространенными фреймворками тестирования.

Совместимость с Espresso

В гибридном приложении компоненты Compose могут находиться в иерархиях View, а View – в composable-функциях Compose (через composable-функцию AndroidView).

Для сопоставления любого из этих типов не требуется никаких специальных действий. Вы сопоставляете представления с помощью onView Espresso, а элементы Compose – с помощью 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()))
}

Как добавить семантику View-scoped для тестирования совместимости Compose

Как ограничить поиск в Compose определенными представлениями

При переносе сложных пользовательских интерфейсов в Compose вы можете столкнуться с идентичными элементами Compose, вложенными в несколько традиционных представлений Android, например в RecyclerView или ViewPager. В таких случаях стандартный поиск Compose, например onNodeWithText("Save"), может завершиться ошибкой "Найдено несколько узлов".

Вместо того чтобы изменять рабочий код для внедрения динамических тестовых тегов, чтобы различать эти элементы, вы можете ограничить область действия теста Compose непосредственно определенным представлением Android.

Используйте onRootWithViewInteraction API для тестового правила. Эта функция принимает Espresso ViewInteraction, позволяя вам использовать Espresso для изоляции определенного контейнера View и выполнения взаимодействий Compose исключительно в этой иерархии.

Как взаимодействовать с элементом списка

Если вам нужно взаимодействовать с элементом Compose в определенной строке RecyclerView, найдите ее с помощью Espresso, а затем ограничьте взаимодействие с Compose этой строкой. Идентичные элементы Compose во всех остальных строках игнорируются.

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

Устранение неоднозначности в ViewPager

Если в памяти одновременно находится несколько фрагментов с идентичными макетами Compose, вы можете ограничить область поиска идентификатором корневого представления определенного фрагмента, чтобы избежать неоднозначности совпадений.

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

Совместимость с UiAutomator

По умолчанию компоненты доступны из UiAutomator только по их дескрипторам (отображаемый текст, описание контента и т. д.). Если вы хотите получить доступ к любой composable-функции, которая использует Modifier.testTag, вам нужно включить семантическое свойство testTagsAsResourceId для поддерева этой функции. Это полезно для composable-функций, у которых нет других уникальных дескрипторов, например для прокручиваемых composable-функций (таких как LazyColumn).

Включите семантическое свойство только один раз в верхней части иерархии composable-функций, чтобы обеспечить доступность всех вложенных composable-функций с Modifier.testTag из 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
    }
}

Любая composable-функция с Modifier.testTag(tag) может быть доступна с помощью By.res(resourceName), использующего тот же tag, что и resourceName.

val device = UiDevice.getInstance(getInstrumentation())

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

Дополнительные ресурсы