本主题介绍如何在评估每个 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.9" debugImplementation "androidx.fragment:fragment-testing-manifest:$fragment_version" androidTestImplementation "androidx.fragment:fragment-testing:$fragment_version" }
Kotlin
dependencies { val fragment_version = "1.8.9" 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())
    }
}
