Android - Independent Fragment UI testing tool Android - Independent Fragment UI testing tool android android

Android - Independent Fragment UI testing tool


I'm am using a custom FragmentTestRule and Espresso to test each of my Fragments in isolation.

I have a dedicated TestActivity that shows the tested Fragments in my app. In my case the Activity only exists in the debug variant because my instrumentation tests run against debug.

TL;DR Use the awesome FragmentTestRule library by @brais-gabin.

1. Create a TestActivity in src/debug/java/your/package/TestActivity.java with a content view where the tested Fragment will be added to:

@VisibleForTestingpublic class TestActivity extends AppCompatActivity {    @Override    protected void onCreate(@Nullable final Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        FrameLayout frameLayout = new FrameLayout(this);        frameLayout.setId(R.id.container);        setContentView(frameLayout);    }}

2. Create a AndroidManifest.xml for the debug variant and declare the TestActivity. This is required to start the TestActivity when testing. Add this Manifest to the debug variant in src/debug/AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android">    <application>                   <activity android:name="your.package.TestActivity"/>    </application></manifest>

3. Create the FragmentTestRule in the androidTest variant at src/androidTest/java/your/test/package/FragmentTestRule.java:

public class FragmentTestRule<F extends Fragment> extends ActivityTestRule<TestActivity> {    private final Class<F> mFragmentClass;    private F mFragment;    public FragmentTestRule(final Class<F> fragmentClass) {        super(TestActivity.class, true, false);        mFragmentClass = fragmentClass;    }    @Override    protected void afterActivityLaunched() {        super.afterActivityLaunched();        getActivity().runOnUiThread(() -> {            try {                //Instantiate and insert the fragment into the container layout                FragmentManager manager = getActivity().getSupportFragmentManager();                FragmentTransaction transaction = manager.beginTransaction();                mFragment = mFragmentClass.newInstance();                transaction.replace(R.id.container, mFragment);                transaction.commit();            } catch (InstantiationException | IllegalAccessException e) {                Assert.fail(String.format("%s: Could not insert %s into TestActivity: %s",                        getClass().getSimpleName(),                        mFragmentClass.getSimpleName(),                        e.getMessage()));            }        });    }    public F getFragment(){        return mFragment;    }}

4. Then you can test Fragments in isolation:

public class MyFragmentTest {    @Rule    public FragmentTestRule<MyFragment> mFragmentTestRule = new FragmentTestRule<>(MyFragment.class);    @Test    public void fragment_can_be_instantiated() {        // Launch the activity to make the fragment visible         mFragmentTestRule.launchActivity(null);        // Then use Espresso to test the Fragment        onView(withId(R.id.an_id_in_the_fragment)).check(matches(isDisplayed()));    }}


If you are using the Navigation Architecture component and you are using a single activity architecture in your app, you can quickly test each fragment by Deep linking to the target fragment (with appropriate arguments) at the beginning of the test.

For example:

@Rule@JvmFieldvar activityRule = ActivityTestRule(MainActivity::class.java)protected fun launchFragment(destinationId: Int,                             argBundle: Bundle? = null) {    val launchFragmentIntent = buildLaunchFragmentIntent(destinationId, argBundle)    activityRule.launchActivity(launchFragmentIntent)}private fun buildLaunchFragmentIntent(destinationId: Int, argBundle: Bundle?): Intent =        NavDeepLinkBuilder(InstrumentationRegistry.getInstrumentation().targetContext)                .setGraph(R.navigation.navigation)                .setComponentName(MainActivity::class.java)                .setDestination(destinationId)                .setArguments(argBundle)                .createTaskStackBuilder().intents[0]

destinationId being the fragment destination id in the navigation graph. Here is an example of a call that would be done once you are ready to launch the fragment:

launchFragment(R.id.target_fragment, targetBundle())private fun targetBundle(): Bundle? {    val bundle = Bundle()    bundle.putString(ARGUMENT_ID, "Argument needed by fragment")    return bundle}

Doing it this way will launch the fragment directly. If your test works, then this proves that your app won't crash when the fragment is deep-linked to. It also ensures that the app will be stable if the process is killed by the system and it tries to rebuild the stack and relaunch the fragment.


I developed FragmentTestRule an Andorid library using the @thaussma's idea. It allows you to test your Fragments in isolation.

You just need to add this:

@Rulepublic FragmentTestRule<?, FragmentWithoutActivityDependency> fragmentTestRule =    FragmentTestRule.create(FragmentWithoutActivityDependency.class);

More information here.