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 Fragment
s in isolation.
You just need to add this:
@Rulepublic FragmentTestRule<?, FragmentWithoutActivityDependency> fragmentTestRule = FragmentTestRule.create(FragmentWithoutActivityDependency.class);