Android - Independent Fragment UI testing tool

Tiago picture Tiago · Nov 11, 2015 · Viewed 8.4k times · Source

I've been looking for a way to test the UI of my Fragments separately (ie, independently from other fragments and activities) but I can't find a way to do it.

In particular, let's say I have Fragment A, Fragment B and Fragment C. The only way (app-wise) to go to Fragment C is by passing through Fragment A and Fragment B first. I am looking for a way to test Fragment C directly (potentially by mocking its dependencies, if any exists), without having to pass through Fragment A and B.

Tools I investigated so far:

  • monkey: only used to generate pseudo-random events through command line. Not what I want.

  • monkeyrunner: it can run Python programs to send event streams to my Android app, but it cannot target a particular Fragment directly with those scripts.

  • Espresso: white-box testing tool. This comes close to what I want, but it still requires passing through Fragment A and B before reaching Fragment C (ie, you need to start your app and then the tests will run from there).

  • UI Automator: black-box testing tool. This also comes close, but again, it requires passing through the previous Fragments before testing the one I want (Fragment C).

Is there any way to test the UI of a Fragment directly?

Answer

thaussma picture thaussma · Jul 15, 2016

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:

@VisibleForTesting
public 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()));
    }
}