本主题介绍如何在评估每个 Fragment 行为的测试中加入框架提供的 API。
fragment 在应用中充当可重复使用的容器,使您能够在各种 activity 和布局配置下呈现相同的界面布局。鉴于 fragment 的多功能性,请务必验证它们是否提供能够高效利用资源的一致体验。请注意以下几点:
- fragment 不应依赖于特定的父 activity 或 fragment。
- 不应创建 fragment 的视图层次结构,除非该 fragment 对用户可见。
为了帮助设置执行这些测试的条件,AndroidX fragment-testing
库提供了 FragmentScenario
类,用于创建 fragment 以及更改其 Lifecycle.State
。
声明依赖项
如需使用 FragmentScenario
,请在fragment-testing-manifest
应用的 build.gradle
文件(使用 debugImplementation
)以及使用 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 Test 设置项目。
创建 Fragment
FragmentScenario
包含下面几种用于在测试中启动 fragment 的方法:
launchInContainer()
,用于测试 fragment 的界面。FragmentScenario
会将 fragment 附加到 activity 的根视图控制器。包含 fragment 的这一 activity 原本为空。launch()
,用于在没有 fragment 界面的情况下进行测试。FragmentScenario
会将这种类型的 fragment 附加到一个空 activity,即没有根视图的 activity。
启动其中一种类型的 fragment 后,FragmentScenario
会将被测 fragment 推动到指定状态。默认情况下,此状态为 RESUMED
,但您可以使用 initialState
参数替换此状态。RESUMED
状态表示 fragment 正在运行且对用户可见。您可以使用 Espresso 界面测试评估有关其界面元素的信息。
以下代码示例展示了如何使用每种方法启动 fragment:
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)
...
}
}
提供依赖项
如果 fragment 具有依赖项,您可以通过向 launchInContainer()
或 launch()
方法提供自定义 FragmentFactory
来提供这些依赖项的测试版本。
@RunWith(AndroidJUnit4::class)
class MyTestSuite {
@Test fun testEventFragment() {
val someDependency = TestDependency()
launchFragmentInContainer {
EventFragment(someDependency)
}
...
}
}
如需详细了解如何使用 FragmentFactory
为 fragment 提供依赖项,请参阅 fragment 管理器。
将 Fragment 推动到新状态
在应用的界面测试中,通常只需启动被测 fragment 并从 RESUMED
状态开始对其进行测试即可。不过,在更精细的单元测试中,您还可以评估 fragment 从一种生命周期状态转换为另一种生命周期状态时的行为。您可以通过向任何 launchFragment*()
函数传递 initialState
参数来指定初始状态。
如需将 fragment 推动到其他生命周期状态,请调用 moveToState()
。此方法支持以下状态作为参数:CREATED
、STARTED
、RESUMED
和 DESTROYED
。此方法会模拟 fragment 或包含 fragment 的 activity 出于任何原因而改变其状态的情况。
以下示例启动了一个处于 INITIALIZED
状态的测试 fragment,然后将其转换为 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.
...
}
}
重新创建 Fragment
如果应用在资源不足的设备上运行,则系统可能会销毁包含 fragment 的 activity。在这种情况下,应用需要在用户返回到它时重新创建该 fragment。如需模拟这种情况,请调用 recreate()
:
@RunWith(AndroidJUnit4::class)
class MyTestSuite {
@Test fun testEventFragment() {
val scenario = launchFragmentInContainer<EventFragment>()
scenario.recreate()
...
}
}
FragmentScenario.recreate()
会销毁 Fragment 及其宿主,然后重新创建它们。当 FragmentScenario
类重新创建被测 Fragment 时,Fragment 会回到它被销毁前所处的生命周期状态。
与界面 Fragment 交互
如需在被测 fragment 中触发界面操作,请使用 Espresso 视图匹配器与视图中的元素交互:
@RunWith(AndroidJUnit4::class)
class MyTestSuite {
@Test fun testEventFragment() {
val scenario = launchFragmentInContainer<EventFragment>()
onView(withId(R.id.refresh)).perform(click())
// Assert some expected behavior
...
}
}
如果您需要对 fragment 本身调用某种方法(如响应选项菜单中的选择),您可以安全地执行此操作,方法是使用 FragmentScenario.onFragment()
并传入 FragmentAction
来获取对 fragment 的引用:
@RunWith(AndroidJUnit4::class)
class MyTestSuite {
@Test fun testEventFragment() {
val scenario = launchFragmentInContainer<EventFragment>()
scenario.onFragment { fragment ->
fragment.myInstanceMethod()
}
}
}
测试对话框操作
FragmentScenario
还支持测试对话框 Fragment。虽然对话框 fragment 具有界面元素,但其布局会填充在单独的窗口中,而不是在 activity 本身中。因此,请使用 FragmentScenario.launch()
测试对话框 Fragment。
以下示例将测试对话框关闭过程:
@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())
}
}