Теперь доступны версии 2 API тестирования Compose (createComposeRule, createAndroidComposeRule, runComposeUiTest, runAndroidComposeUiTest и т. д.), которые позволяют лучше контролировать выполнение сопрограмм. В этом обновлении не дублируется весь API, а только те API, которые создают тестовую среду.
API версии 1 устарели, поэтому мы настоятельно рекомендуем перейти на новые API. Переход на новую версию гарантирует, что ваши тесты соответствуют стандартному поведению сопрограмм и не будут вызывать проблем с совместимостью в будущем. Список устаревших API версии 1 приведен в разделе Сопоставление API.
В API версии 1 использовался UnconfinedTestDispatcher, а в API версии 2 по умолчанию используется StandardTestDispatcher для текущей композиции. Это изменение приводит поведение тестов Compose в соответствие со стандартными API runTest и позволяет явно управлять порядком выполнения сопрограмм.
Как настроить тестовую среду
В API Compose Test версии 2 для настройки среды тестирования используется ComposeUiTestConfig. API, которые создают функции настройки для тестов, например createComposeRule, runComposeUiTest и другие связанные API, принимают ComposeUiTestConfig. Этот объект конфигурации объединяет API, связанные с окружающей средой, например effectContext, runTestContext и testTimeout, в один объект.
Модель конфигурации также управляет inputMode. API Compose test v2 по умолчанию применяют InputMode.Touch в начале каждого теста, чтобы обеспечить детерминизм и предотвратить утечку состояния режима ввода между тестами.
ComposeUiTestConfig входит в состав API Compose testing v2, в которых по умолчанию используется StandardTestDispatcher. Если в ваших тестах используются API версии 1, перед переходом на ComposeUiTestConfig ознакомьтесь с разделом Переход на API тестирования версии 2.
Перенос в ComposeUiTestConfig
В перегрузках для создания функций настройки в тестах несколько перегрузок, принимающих отдельные параметры конфигурации, такие как effectContext, runTestContext или testTimeout, объявлены устаревшими. Замените ее в тестах на таблицу "ComposeUiTestConfig", как показано в следующем примере:
val testConfig = ComposeUiTestConfig( effectContext = EmptyCoroutineContext, runTestContext = EmptyCoroutineContext, testTimeout = 30.seconds ) @get:Rule val rule = createComposeRule(config = testConfig) // OR runComposeUiTest(config = testConfig) {}
Режим ввода по умолчанию
Во время переноса тесты могут не пройти, если они полагаются на режимы ввода, отличные от сенсорного, которые настраиваются с помощью API инструментов до начала теста. В функциях настройки для тестов система по умолчанию применяет InputMode.Touch в начале каждого теста, чтобы повысить детерминированность и предотвратить утечку состояния, переопределяя состояние устройства и настройки, заданные до теста.
Чтобы устранить эту проблему, укажите нужный режим ввода в ComposeUiTestConfig:
class FocusTest { @get:Rule val rule = createComposeRule( config = ComposeUiTestConfig(inputMode = InputMode.Keyboard) ) @Test fun testFocus() {} }
Чтобы настроить режим ввода для отдельных тестовых случаев, а не для всего тестового класса, передайте ComposeUiTestConfig в runComposeUiTest:
class FocusTest { @Test fun testTouchMode() = runComposeUiTest { // Runs with the default InputMode.Touch } @Test fun testKeyboardMode() = runComposeUiTest( ComposeUiTestConfig(inputMode = InputMode.Keyboard) ) { // Runs with InputMode.Keyboard } }
Другие проблемы с переносом и способы их устранения описаны в разделе Частые проблемы и способы их устранения.
Переход на API для тестирования версии 2
При переходе на API версии 2 вы можете использовать функцию Найти и заменить, чтобы обновить импорт пакетов и применить новые изменения диспетчера.
Вы также можете попросить Gemini выполнить переход на версию 2 API тестирования Compose, используя следующий запрос:
Запрос для ИИ
Переход с API для тестирования версии 1 на API для тестирования версии 2
В этом запросе будет использоваться руководство по переходу на API тестирования версии 2.
Migrate to Compose testing v2 APIs using the official
migration guide.В таблице ниже приведены устаревшие API версии 1 и их аналоги в версии 2.
Устаревшая версия (v1) |
Замена (версия 2) |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Обратная совместимость и исключения
Существующие API версии 1 устарели, но продолжают использовать UnconfinedTestDispatcher, чтобы сохранить текущее поведение и избежать критических изменений.
Единственное исключение, когда поведение по умолчанию изменилось:
Диспетчер тестирования по умолчанию, используемый для выполнения композиции в классе AndroidComposeUiTestEnvironment, был изменен с UnconfinedTestDispatcher на StandardTestDispatcher. Это относится к случаям, когда вы создаете экземпляр с помощью конструктора или подкласса AndroidComposeUiTestEnvironment и вызываете этот конструктор.
Ключевое изменение: влияние на выполнение сопрограмм
Основное различие между версиями 1 и 2 API заключается в том, как отправляются сопрограммы:
- API версии 1 (
UnconfinedTestDispatcher). При запуске сопрограммы она выполнялась немедленно в текущем потоке и часто завершалась до того, как выполнялась следующая строка тестового кода. В отличие от рабочего поведения, такое немедленное выполнение может непреднамеренно скрыть реальные проблемы со временем или условия гонки, которые возникли бы в работающем приложении. - API версии 2 (
StandardTestDispatcher). При запуске сопрограмма помещается в очередь и не выполняется, пока тест явным образом не продвинет виртуальные часы. Стандартные API тестирования Compose (например,waitForIdle()) уже выполняют эту синхронизацию, поэтому большинство тестов, использующих эти API, должны работать без изменений.
Частые недочеты и способы их исправить
Если после перехода на версию 2 ваши тесты не проходят, скорее всего, они соответствуют следующему шаблону:
- Сбой. Вы запускаете задачу (например, ViewModel загружает данные), но утверждение сразу же завершается неудачей, поскольку данные ещё находятся в состоянии "Загрузка".
- Причина. В API версии 2 сопрограммы ставятся в очередь, а не выполняются сразу. Задача была поставлена в очередь, но не выполнена до проверки результата.
- Решение. Явно укажите время. Вам нужно явно указать диспетчеру версии 2, когда выполнять работу.
Предыдущий подход
В версии 1 задача запускалась и завершалась немедленно. В версии 2 приведенный ниже код не работает, поскольку loadData() ещё не был выполнен.
// In v1, this launched and finished immediately.
viewModel.loadData()
// In v2, this fails because loadData() hasn't actually run yet!
assertEquals(Success, viewModel.state.value)
Рекомендуемый подход
Используйте waitForIdle или runOnIdle, чтобы выполнить задачи в очереди перед утверждением.
Вариант 1. Используйте waitForIdle, чтобы перевести часы до момента, когда интерфейс будет неактивен. Это позволит убедиться, что сопрограмма выполнена.
viewModel.loadData()
// Explicitly run all queued tasks
composeTestRule.waitForIdle()
assertEquals(Success, viewModel.state.value)
Вариант 2. При использовании runOnIdle блок кода выполняется в потоке UI после того, как интерфейс перейдет в режим ожидания.
viewModel.loadData()
// Run the assertion after the UI is idle
composeTestRule.runOnIdle {
assertEquals(Success, viewModel.state.value)
}
Синхронизация вручную
В сценариях, связанных с синхронизацией вручную, например когда автоматическая перемотка отключена, запуск сопрограммы не приводит к немедленному выполнению, поскольку часы тестирования приостановлены. Чтобы выполнить сопрограммы в очереди, не сдвигая виртуальные часы, используйте API runCurrent(). Выполняются задачи, запланированные на текущее виртуальное время.
composeTestRule.mainClock.scheduler.runCurrent()
В отличие от waitForIdle(), которая переводит часы тестирования до стабилизации интерфейса, runCurrent() выполняет ожидающие задачи, сохраняя текущее виртуальное время. Это позволяет проверять промежуточные состояния, которые в противном случае были бы пропущены, если бы часы были переведены в режим ожидания.
В тестовой среде используется планировщик тестирования. Этот планировщик можно использовать вместе с Kotlin runTest API для синхронизации часов тестирования.
Перенос в runComposeUiTest
Если вы используете тестовые API Compose вместе с API Kotlin runTest, настоятельно рекомендуем перейти на runComposeUiTest.
Предыдущий подход
Использование createComposeRule вместе с runTest создает два отдельных таймера: один для Compose, а другой – для области действия тестовой сопрограммы. В этом случае вам может потребоваться вручную синхронизировать планировщик тестирования.
@get:Rule val composeTestRule = createComposeRule() @Test fun testWithCoroutines() { composeTestRule.setContent { var status by remember { mutableStateOf("Loading...") } LaunchedEffect(Unit) { delay(1000) status = "Done!" } Text(text = status) } // NOT RECOMMENDED // Fails: runTest creates a new, separate scheduler. // Advancing time here does NOT advance the compose clock. // To fix this without migrating, you would need to share the scheduler // by passing 'composeTestRule.mainClock.scheduler' to runTest. runTest { composeTestRule.onNodeWithText("Loading...").assertIsDisplayed() advanceTimeBy(1000) composeTestRule.onNodeWithText("Done!").assertIsDisplayed() } }
Рекомендуемый подход
API runComposeUiTest автоматически выполняет тестовый блок в своей области действия.runTest Тестовые часы синхронизируются со средой Compose, поэтому вам больше не нужно управлять планировщиком вручную.
@Test fun testWithCoroutines() = runComposeUiTest { setContent { var status by remember { mutableStateOf("Loading...") } LaunchedEffect(Unit) { delay(1000) status = "Done!" } Text(text = status) } onNodeWithText("Loading...").assertIsDisplayed() mainClock.advanceTimeBy(1000 + 16 /* Frame buffer */) onNodeWithText("Done!").assertIsDisplayed() } }