Как тестировать @Composable функции
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Тестирование @Composable функций в Jetpack Compose
Тестирование @Composable функций в Jetpack Compose осуществляется с использованием специализированных тестовых API, предоставляемых библиотекой compose-ui-test. Современный подход разделяет тестирование на два основных типа: UI тесты (инструментальные) и unit тесты (модульные) с помощью ComposeTestRule и createComposeRule().
Основные инструменты и зависимости
Для начала необходимо добавить зависимости в build.gradle:
dependencies {
// Тестирование Compose
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
// Для unit тестов Compose
testImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
// Робот-паттерн (рекомендуется)
androidTestImplementation "androidx.test:runner:1.5.2"
}
Типы тестов Compose
1. Инструментальные тесты (UI тесты)
Используют ComposeTestRule для тестирования в эмуляторе или реальном устройстве:
@RunWith(AndroidJUnit4::class)
class MyComposableTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun shouldShowInitialText() {
// Устанавливаем контент
composeTestRule.setContent {
MyComposable(text = "Hello, Compose!")
}
// Проверяем наличие текста
composeTestRule
.onNodeWithText("Hello, Compose!")
.assertIsDisplayed()
}
@Test
fun shouldUpdateTextOnClick() {
var clicked = false
composeTestRule.setContent {
MyButtonComposable(
onClick = { clicked = true }
)
}
// Выполняем действие
composeTestRule
.onNodeWithTag("myButton")
.performClick()
// Проверяем результат
assertTrue(clicked)
}
}
2. Модульные тесты (Unit тесты)
Для тестирования логики без запуска эмулятора используем createComposeRule() для unit-тестов:
class MyComposableUnitTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun testComposableState() {
composeTestRule.setContent {
var count by remember { mutableStateOf(0) }
CounterComposable(count = count, onIncrement = { count++ })
}
// Проверяем начальное состояние
composeTestRule
.onNodeWithText("Count: 0")
.assertExists()
// Симулируем пользовательское действие
composeTestRule
.onNodeWithText("Increment")
.performClick()
// Проверяем обновленное состояние
composeTestRule
.onNodeWithText("Count: 1")
.assertIsDisplayed()
}
}
Ключевые методы тестирования
Поиск элементов в UI:
// По тексту
onNodeWithText("Expected Text")
// По тегу (semantics)
onNodeWithTag("testTag")
// По типу содержимого
onNodeWithContentDescription("contentDesc")
// По селектору
onNode(hasText("Text") and isEnabled())
Проверки (Assertions):
.assertIsDisplayed() // Элемент видим
.assertDoesNotExist() // Элемент отсутствует
.assertIsEnabled() // Элемент активен
.assertIsFocused() // Элемент в фокусе
.assertTextEquals("text") // Проверка точного текста
Действия (Actions):
.performClick() // Клик
.performTextInput("text") // Ввод текста
.performScrollTo() // Прокрутка к элементу
.performGesture { ... } // Сложные жесты
Тестирование состояний и side effects
Для тестирования ViewModels и состояний рекомендуется использовать TestDispatcher:
@Test
fun testComposableWithViewModel() {
val testDispatcher = StandardTestDispatcher()
val viewModel = MyViewModel(testDispatcher)
composeTestRule.setContent {
MyScreen(viewModel = viewModel)
}
// Запускаем корутины
testDispatcher.scheduler.advanceUntilIdle()
// Проверяем результаты
composeTestRule
.onNodeWithText("Loaded data")
.assertIsDisplayed()
}
Паттерн Compose Test Robot
Для улучшения читаемости и поддерживаемости тестов рекомендуется использовать Robot-паттерн:
@Test
fun loginScreen_shouldShowError_onInvalidCredentials() {
composeTestRule.setContent {
LoginScreen()
}
loginScreenRobot(composeTestRule) {
// Действия
enterEmail("invalid@email")
enterPassword("123")
clickLogin()
// Проверки
verifyErrorDisplayed()
verifyLoginButtonDisabled()
}
}
class LoginScreenRobot(
private val composeTestRule: ComposeTestRule
) {
fun enterEmail(email: String) {
composeTestRule
.onNodeWithTag("emailField")
.performTextInput(email)
}
fun verifyErrorDisplayed() {
composeTestRule
.onNodeWithText("Invalid email format")
.assertIsDisplayed()
}
}
Лучшие практики тестирования
- Используйте семантические теги (
Modifier.testTag("identifier")) вместо поиска по текстам, которые могут меняться - Тестируйте поведение, а не реализацию - фокус на том, что видит пользователь
- Разделяйте тесты на мелкие сценарии - один тест = один сценарий использования
- Используйте правило
createAndroidComposeRule()для тестов, требующих активности - Мокируйте зависимости для изоляции тестируемого компонента
- Тестируйте разные состояния:
- Состояния загрузки
- Состояния ошибок
- Пустые состояния
- Разные ориентации экрана (при необходимости)
Тестирование тем и конфигураций
Для тестирования различных конфигураций (тем, размеров экрана) используйте DeviceConfigurationOverride:
@Test
fun testComposableInDarkTheme() {
composeTestRule.setContent {
DeviceConfigurationOverride(
DeviceConfigurationOverride.ForcedSize(DpSize(360.dp, 800.dp))
) {
DeviceConfigurationOverride(
DeviceConfigurationOverride.UiMode(UiMode.NIGHT_YES)
) {
MyComposable()
}
}
}
}
Тестирование @Composable функций требует понимания реактивной природы Compose и правильной работы с состояниями. Современные инструменты предоставляют мощные возможности для создания надежных тестов, которые помогают поддерживать качество UI-слоя приложения.