Распространенные шаблоны

Вы можете протестировать приложение Compose, используя хорошо зарекомендовавшие себя подходы и шаблоны.

Тестирование в изоляции

ComposeTestRule позволяет запустить действие, показывающее любую composable-функцию: все приложение, один экран или небольшой элемент. Также рекомендуется убедиться, что ваши composable-функции правильно инкапсулированы и работают независимо, что упрощает и делает более целенаправленным тестирование пользовательского интерфейса.

Это не значит, что нужно создавать только модульные тесты интерфейса. Тесты пользовательского интерфейса, охватывающие большие части вашего пользовательского интерфейса, также очень важны.

Как получить доступ к заданию и ресурсам после настройки собственного контента

Часто вам нужно задать тестируемый контент с помощью composeTestRule.setContent, а также получить доступ к ресурсам действия, например чтобы убедиться, что отображаемый текст соответствует строковому ресурсу. Однако вы не можете вызвать setContent для правила, созданного с помощью createAndroidComposeRule(), если действие уже вызывает его.

Для этого обычно создается объект AndroidComposeTestRule с помощью пустого действия, например ComponentActivity.

class MyComposeTest {

    @get:Rule
    val composeTestRule = createAndroidComposeRule<ComponentActivity>()

    @Test
    fun myTest() {
        // Start the app
        composeTestRule.setContent {
            MyAppTheme {
                MainScreen(uiState = exampleUiState, /*...*/)
            }
        }
        val continueLabel = composeTestRule.activity.getString(R.string.next)
        composeTestRule.onNodeWithText(continueLabel).performClick()
    }
}

Обратите внимание, что в файл AndroidManifest.xml приложения нужно добавить ComponentActivity. Для этого добавьте в модуль следующую зависимость:

debugImplementation("androidx.compose.ui:ui-test-manifest:$compose_version")

Свойства пользовательской семантики

Вы можете создать пользовательские свойства семантики, чтобы предоставлять информацию для тестов. Для этого определите новый SemanticsPropertyKey и сделайте его доступным с помощью SemanticsPropertyReceiver.

// Creates a semantics property of type Long.
val PickedDateKey = SemanticsPropertyKey<Long>("PickedDate")
var SemanticsPropertyReceiver.pickedDate by PickedDateKey

Теперь используйте это свойство в модификаторе semantics:

val datePickerValue by remember { mutableStateOf(0L) }
MyCustomDatePicker(
    modifier = Modifier.semantics { pickedDate = datePickerValue }
)

Чтобы проверить значение свойства, используйте SemanticsMatcher.expectValue:

composeTestRule
    .onNode(SemanticsMatcher.expectValue(PickedDateKey, 1445378400)) // 2015-10-21
    .assertExists()

Проверка восстановления состояния

Убедитесь, что состояние элементов Compose правильно восстанавливается при повторном создании действия или процесса. Выполняйте такие проверки без повторного создания действия с помощью класса StateRestorationTester.

Этот класс позволяет симулировать повторное создание composable-функции. Это особенно полезно для проверки реализации rememberSaveable.


class MyStateRestorationTests {

    @get:Rule
    val composeTestRule = createComposeRule()

    @Test
    fun onRecreation_stateIsRestored() {
        val restorationTester = StateRestorationTester(composeTestRule)

        restorationTester.setContent { MainScreen() }

        // TODO: Run actions that modify the state

        // Trigger a recreation
        restorationTester.emulateSavedInstanceStateRestore()

        // TODO: Verify that state has been correctly restored.
    }
}

Тестирование разных конфигураций устройств

Приложения для Android должны адаптироваться к различным условиям: размерам окон, региональным настройкам, размерам шрифтов, тёмным и светлым темам и т. д. Большинство этих условий основаны на значениях, заданных пользователем на уровне устройства и доступных в текущем экземпляре Configuration. Тестировать разные конфигурации непосредственно в тесте сложно, поскольку тест должен настраивать свойства на уровне устройства.

DeviceConfigurationOverride – это API, предназначенный только для тестирования. Он позволяет имитировать различные конфигурации устройств для тестируемого контента @Composable.

Объект-компаньон DeviceConfigurationOverride имеет следующие функции расширения, которые переопределяют свойства конфигурации на уровне устройства:

Чтобы применить определенное переопределение, оберните тестируемый контент в вызов функции верхнего уровня DeviceConfigurationOverride(), передав переопределение в качестве параметра.

Например, в следующем коде применяется переопределение DeviceConfigurationOverride.ForcedSize(), чтобы изменить плотность локально и принудительно отобразить composable-функцию MyScreen в большом горизонтальном окне, даже если устройство, на котором выполняется тест, не поддерживает этот размер окна напрямую:

composeTestRule.setContent {
    DeviceConfigurationOverride(
        DeviceConfigurationOverride.ForcedSize(DpSize(1280.dp, 800.dp))
    ) {
        MyScreen() // Will be rendered in the space for 1280dp by 800dp without clipping.
    }
}

Чтобы применить несколько переопределений, используйте DeviceConfigurationOverride.then():

composeTestRule.setContent {
    DeviceConfigurationOverride(
        DeviceConfigurationOverride.FontScale(1.5f) then
            DeviceConfigurationOverride.FontWeightAdjustment(200)
    ) {
        Text(text = "text with increased scale and weight")
    }
}

Специальная обработка сбоев в тестах

Отладка тестов пользовательского интерфейса часто требует проверки состояния экрана и иерархии композиции при сбое утверждения.

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

Настройте обработку сбоев

Чтобы настроить обработку сбоев, передайте объект TestFailurePolicy в ComposeUiTestConfig.

Как получить встроенные артефакты сбоя

Чтобы при сбое тестирования создавались скриншоты и иерархии интерфейса, задайте свойства screenshotCaptureMode и uiHierarchyCaptureMode в файле TestFailurePolicy.

class MyComposeTest {
    private val customConfig = ComposeUiTestConfig(
        failurePolicy = TestFailurePolicy(
            screenshotCaptureMode = TestFailurePolicy.CaptureMode.Enabled,
            uiHierarchyCaptureMode = TestFailurePolicy.CaptureMode.Enabled
        )
    )

    @Test
    fun myFirstTest() = runComposeUiTest(config = customConfig) {
        // ...
    }
}

Как использовать собственный обработчик ошибок

Чтобы задать пользовательское поведение для сбоев при тестировании, добавьте TestFailureHandler в список failureHandlers. Каждый обработчик получает объект FailureContext, который содержит исходную ошибку и список артефактов, созданных предыдущими обработчиками конвейера.

class MyComposeTestWithHandler {
    private val customConfig = ComposeUiTestConfig(
        failurePolicy = TestFailurePolicy(
            screenshotCaptureMode = TestFailurePolicy.CaptureMode.Enabled,
            failureHandlers = listOf(
                TestFailureHandler { context ->
                    val screenshot = context.artifacts.firstOrNull {
                        it.type == FailureArtifact.Type.Screenshot
                    }
                    // ...
                }
            )
        )
    )

    @get:Rule
    val rule = createComposeRule(config = customConfig)

    // ...
}

Как настроить запись для всего набора тестов

Чтобы включить сбор артефактов сбоев для всего набора тестов, передайте аргументы средства запуска инструментирования в файле build.gradle модуля. Это избавляет от необходимости обновлять отдельные тестовые файлы.

// build.gradle.kts
android {
    defaultConfig {
        // ...
        testInstrumentationRunnerArguments["androidx.compose.ui.test.failure.isUiHierarchyCaptureEnabled"] = "true"
        testInstrumentationRunnerArguments["androidx.compose.ui.test.failure.isScreenshotCaptureEnabled"] = "true"
    }
}

Приоритет конфигураций

Фреймворк оценивает как локальные аргументы TestFailurePolicy, так и аргументы средства запуска на уровне набора:

  • Если вы явно зададите режим CaptureMode.Enabled/Disabled в тестовом файле failurePolicy, это переопределит аргумент средства запуска.
  • Если вы зададите режим CaptureMode.Unspecified (или оставите для failurePolicy значение null), фреймворк вернется к аргументу runner.

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