Тестирование фрагментов

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

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

  • Фрагмент не должен зависеть от определенного родительского действия или фрагмента.
  • Не создавайте иерархию View фрагмента, если он не виден пользователю.

Чтобы упростить настройку условий для выполнения этих тестов, библиотека AndroidX fragment-testing предоставляет класс FragmentScenario для создания фрагментов и изменения их Lifecycle.State.

Объявление зависимостей

Чтобы использовать FragmentScenario, определите артефакт fragment-testing-manifest в файле build.gradle приложения с помощью debugImplementation, а артефакт fragment-testing – с помощью androidTestImplementation, как показано в примере ниже.

классный

dependencies {
    def fragment_version = "1.9.1"

    debugImplementation "androidx.fragment:fragment-testing-manifest:$fragment_version"

    androidTestImplementation "androidx.fragment:fragment-testing:$fragment_version"
}

Котлин

dependencies {
    val fragment_version = "1.9.1"

    debugImplementation("androidx.fragment:fragment-testing-manifest:$fragment_version")

    androidTestImplementation("androidx.fragment:fragment-testing:$fragment_version")
}

В примерах тестирования на этой странице используются утверждения из библиотек Espresso и Truth. Информацию о других доступных библиотеках тестирования и утверждения можно найти в статье Как настроить проект для AndroidX Test.

Создание фрагмента

FragmentScenario включает следующие методы запуска фрагментов в тестах:

  • launchInContainer() для тестирования пользовательского интерфейса фрагмента. FragmentScenario прикрепляет фрагмент к корневому контроллеру представления действия. В противном случае это действие будет пустым.
  • launch(), чтобы протестировать фрагмент без пользовательского интерфейса. FragmentScenario прикрепляет этот тип фрагмента к пустому действию, у которого нет корневого представления.

После запуска одного из этих типов фрагментов FragmentScenario переводит тестируемый фрагмент в указанное состояние. По умолчанию это RESUMED, но вы можете переопределить это значение с помощью аргумента initialState. Состояние RESUMED указывает, что фрагмент запущен и виден пользователю. Вы можете оценить информацию об элементах интерфейса с помощью тестов Espresso UI.

В следующих примерах кода показано, как запустить фрагмент с помощью каждого метода:

Пример launchInContainer()

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        // The "fragmentArgs" argument is optional.
        val fragmentArgs = bundleOf(“selectedListItem” to 0)
        val scenario = launchFragmentInContainer<EventFragment>(fragmentArgs)
        ...
    }
}

Пример launch()

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        // The "fragmentArgs" arguments are optional.
        val fragmentArgs = bundleOf("numElements" to 0)
        val scenario = launchFragment<EventFragment>(fragmentArgs)
        ...
    }
}

Укажите зависимости

Если у фрагментов есть зависимости, вы можете предоставить их тестовые версии, передав пользовательский объект FragmentFactory в метод launchInContainer() или launch().

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        val someDependency = TestDependency()
        launchFragmentInContainer {
            EventFragment(someDependency)
        }
        ...
    }
}

Дополнительную информацию о том, как использовать FragmentFactory для предоставления зависимостей фрагментам, можно найти в статье Менеджер фрагментов.

Перевести фрагмент в новое состояние

В тестах пользовательского интерфейса приложения обычно достаточно запустить тестируемый фрагмент и начать его тестирование из состояния RESUMED. Однако в более детальных модульных тестах вы также можете оценить поведение фрагмента при его переходе из одного состояния жизненного цикла в другое. Вы можете указать начальное состояние, передав аргумент initialState любой из функций launchFragment*().

Чтобы перевести фрагмент в другое состояние жизненного цикла, вызовите moveToState(). Этот метод поддерживает следующие состояния в качестве аргументов: CREATED, STARTED, RESUMED и DESTROYED. Этот метод имитирует ситуацию, когда фрагмент или действие, содержащее фрагмент, по какой-либо причине меняет свое состояние.

В следующем примере тестовый фрагмент запускается в состоянии INITIALIZED, а затем переводится в состояние RESUMED:

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        val scenario = launchFragmentInContainer<EventFragment>(
            initialState = Lifecycle.State.INITIALIZED
        )
        // EventFragment has gone through onAttach(), but not onCreate().
        // Verify the initial state.
        scenario.moveToState(Lifecycle.State.RESUMED)
        // EventFragment moves to CREATED -> STARTED -> RESUMED.
        ...
    }
}

Создать фрагмент заново

Если приложение запущено на устройстве с ограниченными ресурсами, система может уничтожить действие, содержащее фрагмент. В этом случае при возвращении пользователя в приложение фрагмент нужно будет создать заново. Чтобы смоделировать эту ситуацию, вызовите recreate():

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        val scenario = launchFragmentInContainer<EventFragment>()
        scenario.recreate()
        ...
    }
}

FragmentScenario.recreate() уничтожает фрагмент и его хост, а затем создает их заново. Когда класс FragmentScenario воссоздает тестируемый фрагмент, он возвращается в состояние жизненного цикла, в котором находился до уничтожения.

Взаимодействие с фрагментами пользовательского интерфейса

Чтобы запустить действия в пользовательском интерфейсе тестируемого фрагмента, используйте Espresso ViewMatchers для взаимодействия с элементами в представлении:

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        val scenario = launchFragmentInContainer<EventFragment>()
        onView(withId(R.id.refresh)).perform(click())
        // Assert some expected behavior
        ...
    }
}

Если вам нужно вызвать метод для самого фрагмента, например чтобы отреагировать на выбор в меню параметров, вы можете сделать это, получив ссылку на фрагмент с помощью FragmentScenario.onFragment() и передав FragmentAction:

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        val scenario = launchFragmentInContainer<EventFragment>()
        scenario.onFragment { fragment ->
            fragment.myInstanceMethod()
        }
    }
}

Тестирование действий в диалоге

FragmentScenario также поддерживает тестирование фрагментов диалоговых окон. Хотя фрагменты диалогов имеют элементы пользовательского интерфейса, их макет заполняется в отдельном окне, а не в самом действии. Поэтому для тестирования фрагментов диалогов используйте FragmentScenario.launch().

В следующем примере тестируется процесс закрытия диалогового окна:

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testDismissDialogFragment() {
        // Assumes that "MyDialogFragment" extends the DialogFragment class.
        with(launchFragment<MyDialogFragment>()) {
            onFragment { fragment ->
                assertThat(fragment.dialog).isNotNull()
                assertThat(fragment.requireDialog().isShowing).isTrue()
                fragment.dismiss()
                fragment.parentFragmentManager.executePendingTransactions()
                assertThat(fragment.dialog).isNull()
            }
        }

        // Assumes that the dialog had a button
        // containing the text "Cancel".
        onView(withText("Cancel")).check(doesNotExist())
    }
}