Android - Independent Fragment UI testing tool

旧巷老猫 提交于 2019-11-29 21:48:27

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

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:

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

More information here.

You can use Robotium.This is for android UI testing.

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
@JvmField
var 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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!