Exploring the Android Fragment Scenario component

I’m always curious about what tools and features are coming up next in android — and to be sure I don’t miss out on any of this I like to keep an eye on release notes over on the android developer site. Two that caught my eye recently where the fragment-1.1.0-alpha01 and fragment-testing-1.1.0-alpha01 releases, within the testing side of things we see a new FragmentScenario component which provides us with an easy way to test our fragments in isolation. In this post I want to take a quick dive into this so we can get it integrated into our application tests.

Note: This is only currently available in the alpha release.



When it comes to the automated testing of our android applications we may be using espresso to satisfy this requirement. If so, chances are that you’ve used espresso to test an activity in your application — this may have involved things such launching an activity, providing extras for the activity intent and checking some UI component state. Whilst this has always been well and good when it comes to activities, how have you handled the testing of fragments in your application? Chances are it’s been something along the lines of:

  • Launching the activity of your application which houses the fragment that you want to test
  • Creating a custom test rule that allows you to launch the fragment that you want to test in isolation

Whilst these two ways work, they both have their flaws. To begin with, launching the activity housing the fragment means that you are not testing that fragment in isolation — your test should only care about that fragment itself and not the parent containers of it. For example, if you’re using a bottom navigation view inside of an activity that also manages some behind-the-scenes state (such as an authenticated user for example) then this data is going to need to be handled within the test for your fragment — you may then run into having to mock many different requests for some things that the fragment doesn’t even care for. And then what if this housing activity then changes, are our fragment tests going to break as well as the activity tests? Whilst these are only simple points I hope it displays that like our application code, our tests should focus on their responsibility and remain lightweight. This will help to ensure that our tests remain maintainable, readable and also less likely to break as we move forward.

Moving onto the second point — this one is definitely a large improvement on the first. This allows us to launch our fragment in isolation and focus on what we are testing. This will either require using an external dependency (such as https://github.com/novoda/espresso-support) or writing your own implementation. However, these require either adding a dependency to your application or adding a small bit of boilerplate to your projects every time that you want to setup isolated testing. Not a huge deal, but it’s worth bearing these things in mind.

Now, with the latest release of fragment-1.1.0-alpha01 and fragment-testing-1.1.0-alpha01 we see this new FragmentScenario component which allows us to no longer have to worry about how we will be testing our fragments. This new component handles all of this responsibility for us , let’s take a look at how we can write some fragment tests using this component.


Before we can get setup with this new functionality, we need to go ahead and add the following dependencies to our build.gradle file:

implementation 'androidx.fragment:fragment:1.1.0-alpha01'
debugImplementation 'androidx.fragment:fragment-testing:1.1.0-alpha01'

Now that we’ve done so, we can go ahead and improve our fragment testing. Let’s begin by taking a quick look at some code which can be used to launch a desired fragment and then check that a given view is displayed within it:

@Test
fun tournamentsContainerIsDisplayed() {
launchFragmentInContainer<TournamentsFragment>()

onView(withId(R.id.recycler_tournaments))
.check(matches(isDisplayed()))
}

The key here is this launchFragmentInContainer() function that we call — this is what is used to launch our desired fragment in an isolated environment (essentially an empty activity that houses that fragment). You’ll notice that this function takes a fragment class as its type — this class reference is later used to perform the launch. All that this functionality does it take the given fragment and launch it inside of an internal EmptyFragmentActivity class — placing the fragment inside of the root view container, android.R.id.content. This is likely similar to the boilerplate code that you would have previously had inside of your application if you were tested fragments in an isolated manner.

Once our fragment has been launched using this function, we can interact with it just as we would in our other espresso tests.


The call to launchFragmentInContainer() can also be done with the addition of two arguments:

  • fragment arguments — Provide additional arguments that can be passed to your fragment to configure its behaviour. It’s likely that some of your fragments make use of fragment arguments, using this property provides an easy way for you to test the behaviour given these conditional values
val args = Bundle().apply {
putString(ARG_FRAGMENT_MODE, "some_mode_key")
}
  • fragment factory — The androidx.fragment 1.1.0-alpha01 release now allows fragments to be constructed using a FragmentFactory with a FragmentManager instance, this allows you to provide a way to depict how the fragment should be instantiated. So that the outcome of this factory can also be tested, this factory can be passed in as an argument.
val factory = SomeFragmentFactory()

With these in mind, we could then launch our fragment with the additional properties that we have provided:

launchFragmentInContainer<TournamentsFragment>(args, factory)

Now in some cases you may want to perform trigger operations on the fragment that you have launched in your tests. When calling the launchFragmentInContainer method, we get back an instance of the FragmentScenario class.

val scenario = launchFragmentInContainer<TournamentsFragment>()

Now that we have access to this Fragment Scenario, there are a couple of different things that we can do to manipulate and test our fragment further.

Trigger fragment functions

In some situations your fragment might contain a public function that external classes may call — maybe this function refreshes content or maybe it handles some application state change. Either way, this new API addition allows us to easily interact with our launched fragment:

val scenario = launchFragmentInContainer<TournamentsFragment>()
scenario.onFragment {
it
.onUserSignedOut()
}

This onFragment function allows us to trigger our desired action on our fragment. Within this call, the API uses the fragment manager to locate our fragment and then perform the action that we have requested. This will be useful for testing public functions that can be triggered on our fragments.

Fragment recreation

If in some case we may require to reset the state of our fragment, then we can do so via the use of the recreate() function:

val scenario = launchFragmentInContainer<TournamentsFragment>()
scenario.recreate()

When this is called, the empty activity that is hosting our fragment is recreated which in turn will result in the fragments state being reset to the previous state that it was in.

Changing states

In some of our fragments we may have different things happen during the its lifecycle — maybe we trigger some specific event during resume, and maybe a different one once the fragment is started. In these situations we can make use of the moveToState() function to programatically trigger a state change

val scenario = launchFragmentInContainer<TournamentsFragment>()
scenario.moveToState(Lifecycle.State.RESUMED)

When called, this will retrieve our current fragment and force the state to be set using our given argument.


In this post we’ve taken a look at the new Fragment Scenario component and how we can use it to test our fragments in an isolated environment. This new API addition will help us to improve our fragment tests and keep things simplified moving forwards. If you have any questions or comments on the Fragment Scenario, then please do reach out!