本主题介绍如何在评估每个片段的行为的测试中包含框架提供的 API。
片段充当您应用中的可重用容器,允许您在各种活动和布局配置中呈现相同的用户界面布局。鉴于片段的多功能性,验证它们提供一致且资源高效的体验非常重要。请注意以下几点
- 您的片段不应依赖于特定的父活动或片段。
- 除非片段对用户可见,否则您不应创建片段的视图层次结构。
为了帮助您设置执行这些测试的条件,AndroidX fragment-testing
库提供了 FragmentScenario
类来创建片段并更改它们的 Lifecycle.State
.
声明依赖项
要使用 FragmentScenario
,请在您应用的 build.gradle
文件中使用 debugImplementation
定义 fragment-testing-manifest
工件,并使用 androidTestImplementation
定义 fragment-testing
工件,如下面的示例所示
Groovy
dependencies { def fragment_version = "1.8.3" debugImplementation "androidx.fragment:fragment-testing-manifest:$fragment_version" androidTestImplementation "androidx.fragment:fragment-testing:$fragment_version" }
Kotlin
dependencies { val fragment_version = "1.8.3" debugImplementation("androidx.fragment:fragment-testing-manifest:$fragment_version") androidTestImplementation("androidx.fragment:fragment-testing:$fragment_version") }
本页上的测试示例使用来自 Espresso 和 Truth 库的断言。有关其他可用测试和断言库的信息,请参阅 为 AndroidX 测试设置项目。
创建片段
FragmentScenario
包括以下方法,用于在测试中启动片段
launchInContainer()
,用于测试片段的用户界面。FragmentScenario
将片段附加到活动的根视图控制器。此包含活动在其他方面是空的。launch()
,用于在没有片段用户界面的情况下进行测试。FragmentScenario
将这种类型的片段附加到空活动,即没有根视图的活动。
启动这些碎片类型之一后,FragmentScenario
会将测试中的碎片驱动到指定状态。默认情况下,此状态为 RESUMED
,但您可以使用 initialState
参数覆盖它。RESUMED
状态表示碎片正在运行,并且对用户可见。您可以使用 Espresso UI 测试 评估其 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)
...
}
}
提供依赖项
如果您的碎片有依赖项,您可以通过向 launchInContainer()
或 launch()
方法提供自定义 FragmentFactory
来提供这些依赖项的测试版本。
@RunWith(AndroidJUnit4::class)
class MyTestSuite {
@Test fun testEventFragment() {
val someDependency = TestDependency()
launchFragmentInContainer {
EventFragment(someDependency)
}
...
}
}
有关使用 FragmentFactory
向碎片提供依赖项的更多信息,请参阅 碎片管理器。
将碎片驱动到新状态
在应用程序的 UI 测试中,通常足以启动测试中的碎片并从 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
类重新创建测试中的碎片时,碎片将返回到其被销毁之前所在的生命周期状态。
与 UI 碎片交互
要在测试中的碎片中触发 UI 操作,请使用 Espresso 视图匹配器 与视图中的元素进行交互。
@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
还支持测试 对话框碎片。虽然对话框碎片有 UI 元素,但它们的布局是在单独的窗口中填充的,而不是在活动本身中填充的。因此,请使用 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())
}
}