测试您的片段

本主题介绍如何在测试中包含框架提供的 API,这些测试评估每个片段的行为。

片段充当应用中的可重用容器,允许您在各种活动和布局配置中呈现相同的用户界面布局。鉴于片段的多功能性,验证它们提供一致且资源高效的体验非常重要。请注意以下几点:

  • 您的片段不应依赖于特定的父活动或片段。
  • 除非片段对用户可见,否则不应创建片段的视图层次结构。

为了帮助设置执行这些测试的条件,AndroidX fragment-testing 库提供了 FragmentScenario 类来创建片段并更改其 Lifecycle.State

声明依赖项

要使用 FragmentScenario,请在您的应用的 build.gradle 文件中使用 debugImplementation 定义 fragment-testing-manifest 工件,并使用 androidTestImplementation 定义 fragment-testing 工件,如下例所示:

Groovy

dependencies {
    def fragment_version = "1.8.5"

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

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

Kotlin

dependencies {
    val fragment_version = "1.8.5"

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

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

此页面上的测试示例使用来自 EspressoTruth 库的断言。有关其他可用测试和断言库的信息,请参阅 为 AndroidX Test 设置项目

创建片段

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()。此方法支持以下状态作为参数:CREATEDSTARTEDRESUMEDDESTROYED。此方法模拟了包含您的片段的片段或活动由于任何原因更改其状态的情况。

以下示例在 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())
    }
}