Доступны версии v2 API для тестирования Compose (createComposeRule, createAndroidComposeRule, runComposeUiTest, runAndroidComposeUiTest и т. д.), которые позволяют лучше контролировать выполнение сопрограмм. Это обновление не дублирует всю поверхность API. Обновлены только API, которые создают тестовую среду.
Версия 1 API больше не поддерживается, и мы настоятельно рекомендуем перейти на новую версию. Переход на новую версию гарантирует, что ваши тесты соответствуют стандартному поведению сопрограмм, и позволяет избежать проблем с совместимостью в будущем. Список устаревших API версии 1 приведен в разделе Сопоставление API.
В API версии 1 использовался UnconfinedTestDispatcher, а в API версии 2 для текущей композиции по умолчанию используется StandardTestDispatcher. Это изменение приводит поведение тестов Compose в соответствие со стандартными API runTest и обеспечивает явный контроль над порядком выполнения сопрограмм.
Настройте тестовую среду
В Compose Test API версии 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 выполнить перенос на вторую версию API для тестирования Compose, используя следующий запрос:
запрос для ИИ
Переход с API для тестирования версии 1 на версию 2
Этот запрос будет использовать это руководство для перехода на API тестирования версии 2.
Migrate to Compose testing v2 APIs using the official
migration guide.В таблице ниже показано, какие API версии 1 заменяются какими API версии 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 сопрограммы ставятся в очередь, а не выполняются немедленно. Задача была поставлена в очередь, но не была выполнена до проверки результата.
- Решение. Явно задайте время. Вы должны явно указать диспетчеру v2, когда выполнять работу.
Предыдущий подход
В версии 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 после того, как 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() } }