Join us on the livestream at Android Dev Summit on 7-8 November 2018, starting at 10AM PDT!

Test your app's fragments

Fragments serve as reusable containers within your app, allowing you to present the same user interface layout in a variety of activities and layout configurations. Given the versatility of these fragments, it's important to validate that they provide a consistent and resource-efficient experience:

  • Your fragment's appearance should be consistent across layout configurations, including those that support larger screen sizes or the landscape device orientation.
  • Don't create a fragment's view hierarchy unless the fragment is visible to the user.

This document describes how to include framework-provided APIs in the tests that evaluate each fragment's behavior.

Drive a fragment's state

To help set up the conditions for performing these tests, AndroidX provides a library, FragmentScenario, to create fragments and change their state.

Configure testing artifact location

In order to use FragmentScenario as intended, define the fragment-testing artifact in your app's testing APK, as shown in the following code snippet:

app/build.gradle

dependencies {
    // ...
    debugImplementation 'androidx.fragment:fragment-testing:1.1.0-alpha01'
}

Create a fragment

FragmentScenario includes methods for launching the following types of fragments:

The methods also support the following types of fragments:

  • Graphical fragments, which contain a user interface. To launch this type of fragment, call launchFragmentInContainer(). FragmentScenario attaches the fragment to an activity's root view controller. This containing activity is otherwise empty.
  • Non-graphical fragments (sometimes referred to as headless fragments), which store or perform short-term processing on information included in several activities. To launch this type of fragment, call launchFragment(). FragmentScenario attaches this type of fragment to an entirely empty activity, one that doesn't have a root view.

After launching one of these fragment types, FragmentScenario drives the fragment under test to the RESUMED state. This state indicates that the fragment is running. If you're testing a graphical fragment, it's also visible to users, so you can evaluate information about its UI elements using Espresso UI tests.

The following code snippets show how to launch each type of fragment:

Graphical fragment example

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        // The "state" and "factory" arguments are optional.
        val fragmentArgs = Bundle().apply {
            putInt("selectedListItem", 0)
        }
        val factory = MyFragmentFactory()
        val scenario = launchFragmentInContainer<MyFragment>(
                fragmentArgs, factory)
        onView(withId(R.id.text)).check(matches(withText("Hello World!")))
    }
}

Non-graphical fragment example

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        // The "state" and "factory" arguments are optional.
        val fragmentArgs = Bundle().apply {
            putInt("numElements", 0)
        }
        val factory = MyFragmentFactory()
        val scenario = launchFragment<MyFragment>(fragmentArgs, factory)
    }
}

Recreate the fragment

If a device is low on resources, the system might destroy the activity containing your fragment, requiring your app to recreate the fragment when the user returns to your app. To simulate this situation, call recreate():

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

When the FragmentScenario class recreates the fragment under test, the fragment returns to the lifecycle state that it was in before being recreated.

Drive the fragment to a new state

In your app's UI tests, it's usually sufficient to just launch and recreate the fragment under test. In finer-grained unit tests, however, you might also evaluate the fragment's behavior as it transitions from one lifecycle state to another.

To drive the fragment to a different lifecycle state, call moveToState(). This methods supports the following states as arguments: CREATED, STARTED, RESUMED, and DESTROYED. This action simulates a situation where the activity containing your fragment changes its state because it's interrupted by another app or a system action.

An example usage of moveToState() appears in the following code snippet:

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        val scenario = launchFragmentInContainer<MyFragment>()
        scenario.moveToState(State.CREATED)
    }
}

Trigger actions in the fragment

To trigger actions in your fragment under test, use Espresso view matchers to interact with elements in your view:

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        val scenario = launchFragmentInContainer<MyFragment>()
        onView(withId(R.id.refresh))
                .perform(click())
    }
}

If you need to call a method on the fragment itself, such as responding to a selection in the options menu, you can do so safely by implementing FragmentAction:

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEventFragment() {
        val scenario = launchFragmentInContainer<MyFragment>()
        scenario.onFragment(fragment ->
            fragment.onOptionsItemSelected(clickedItem) {
                // Update fragment's state based on selected item.
            }
        }
    }
}