如何使用 Espresso 单独测试片段?

问题描述 投票:0回答:6

我有一个想要测试的Fragment。我创建了一个测试活动,向其中添加此片段并运行一些 Espresso 测试。

但是,Espresso 在 Fragment 内找不到任何视图。它转储视图层次结构并且全部为空。

我不想将片段嵌入到测试活动中。我只想单独测试片段。有人这样做过吗?有类似代码的示例吗?

@RunWith(AndroidJUnit4.class)
class MyFragmentTest {

    @Rule
    public ActivityTestRule activityRule = new ActivityTestRule<>(
        TestActivity.class
    );

    @Test
    public void testView() {
        MyFragment myFragment = startMyFragment();
        myFragment.onEvent(new MyEvent());
        // MyFragment has a RecyclerView
        // onEvent is an EventBus callback that contains no data in this test
        // I want the Fragment to display an empty list text and hide the RecyclerView

        onView(withId(R.id.my_empty_text)).check(matches(isDisplayed()));
        onView(withId(R.id.my_recycler)).check(doesNotExist()));
    }

    private MyFragment startMyFragment() {
        FragmentActivity activity = (FragmentActivity) activityRule.getActivity();
        FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
        MyFragment myFragment = new MyFragment();
        transaction.add(myFragment, "myfrag");
        transaction.commit();
        return myFragment;
    }
}
android android-fragments android-espresso
6个回答
7
投票

我会按照以下方式做 创建一个ViewAction如下:

public static ViewAction doTaskInUIThread(final Runnable r) {
    return new ViewAction() {
        @Override
        public Matcher<View> getConstraints() {
            return isRoot();
        }

        @Override
        public String getDescription() {
            return null;
        }

        @Override
        public void perform(UiController uiController, View view) {
            r.run();
        }
    };
}

然后使用下面的代码来启动应该在 UI 线程中运行的代码

onView(isRoot()).perform(doTaskInUIThread(new Runnable() {
        @Override
        public void run() {
            //Code to add your fragment or anytask that you want to do from UI Thread
        }
    }));

下面是添加片段视图层次结构的测试用例示例

    @Test
public void testSelectionOfTagsAndOpenOtherPage() throws Exception{

    Runnable r = new Runnable() {
        @Override
        public void run() {
            //Task that need to be done in UI Thread (below I am adding a fragment)

        }
    };
    onView(isRoot()).perform(doTaskInUIThread(r));

}

6
投票
public class VoiceFullScreenTest {
    @Rule
    public ActivityTestRule activityRule = new ActivityTestRule<>(
            TestActivity.class);

    @Test
    public void fragment_can_be_instantiated() {
        activityRule.getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                VoiceFragment voiceFragment = startVoiceFragment();
            }
        });
        // Then use Espresso to test the Fragment
        onView(withId(R.id.iv_record_image)).check(matches(isDisplayed()));
    }

    private VoiceFragment startVoiceFragment() {
        TestActivity activity = (TestActivity) activityRule.getActivity();
        FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
        VoiceFragment voiceFragment = new VoiceFragment();
        transaction.add(voiceFragment, "voiceFragment");
        transaction.commit();
        return voiceFragment;
    }


}

您可以如上所述从 UI 线程启动片段。


6
投票

您可以使用

androidx.fragment:fragment-testing
库。在测试方法中启动片段非常简单:

val fragmentArgs = Bundle()
androidx.fragment.app.testing.launchFragmentInContainer<MyFragment>(fragmentArgs)

您可以在测试您的片段Android开发人员指南中找到有关此库的更多信息。


4
投票

您可以使用FragmentTestRule

您必须使用:

 而不是常规的 
ActivityTestRule

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

您可以在这篇博文中找到更多详细信息


1
投票

您可能忘记将片段注入视图层次结构中。尝试在

TestActivity
布局中为片段定义支架容器(例如 id 为
FrameLayout
fragment_container
),然后使用
add(myFragment, "tag")
此方法
)而不是仅使用 add(R.id.fragment_container, myFragment, "tag")。我想您也可以使用具有相同签名的
replace
方法。


0
投票

我不想将片段嵌入到测试活动中。我只想 单独测试 Fragment。有人这样做过吗?有没有 有类似代码的示例吗?

保持片段隔离是正确的方法。但不幸的是,如果没有宿主,片段就无法存在。正如 @Adil Hussain上面 的回答,

androidx.fragment:fragment-testing
库提供了合适的工具来解决此问题。

为了测试片段的用户界面,

androidx.fragment.app.testing.FragmentScenario
类包含一些重载的
launchInContainer()
静态方法 它将片段附加到特殊的空活动的根视图控制器。此活动专为测试目的而设计,使您无需创建自己的测试活动、将其注册到应用程序的清单中等。

在我的项目中,我实现了支持类

FragmentScenarioRule.java
(如下所示),它将
org.junit.rules.ExternalResource
扩展为 JUnit4 测试规则(以防万一,此实现类似于 ActivityTestRule 中的
androidx.test:rules
 类) 
图书馆)。

我不需要通过片段的构造函数注入任何依赖项,因此我只需使用默认构造函数来创建片段。但是您可以改进此实现以提供您自己的规则

FragmentFactory
。但我需要执行一些初始操作,因此我向此规则添加了拦截生命周期事件并执行适当回调的功能。

FragmentScenarioRule.java

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.Consumer;
import androidx.core.util.Supplier;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentFactory;
import androidx.fragment.app.testing.FragmentScenario;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleEventObserver;

import org.junit.rules.ExternalResource;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public final class FragmentScenarioRule<T extends Fragment> extends ExternalResource {
    @NonNull
    private final List<LifecycleEventObserver> mLifecycleObservers = new ArrayList<>();
    @NonNull
    private final Supplier<FragmentScenario<T>> mScenarioSupplier;
    @Nullable
    private FragmentScenario<T> mScenario;

    public FragmentScenarioRule(@NonNull final Class<T> fragmentClass) {
        mScenarioSupplier = () -> FragmentScenario.launchInContainer(
                fragmentClass,
                null,
                getFragmentFactory(fragmentClass)
        );
    }

    @NonNull
    public FragmentScenarioRule<T> registerCallback(@NonNull final Lifecycle.Event event,
                                                    @NonNull final Consumer<? super T> callback) {
        mLifecycleObservers.add((lifecycleOwner, lifecycleEvent) -> {
            if (lifecycleEvent == event) {
                //noinspection unchecked
                callback.accept((T) lifecycleOwner);
            }
        });

        return this;
    }

    @Override
    protected void before() {
        mScenario = mScenarioSupplier.get();
    }

    @Override
    protected void after() {
        Objects.requireNonNull(mScenario).close();
    }

    @NonNull
    public FragmentScenario<T> getScenario() {
        return Objects.requireNonNull(mScenario);
    }

    @NonNull
    private FragmentFactory getFragmentFactory(@NonNull final Class<T> fragmentClass) {
        return new FragmentFactory() {
            @NonNull
            @Override
            public Fragment instantiate(@NonNull final ClassLoader classLoader,
                                        @NonNull final String className) {
                Class<? extends Fragment> requeredClass = loadFragmentClass(classLoader, className);

                if (requeredClass != fragmentClass) {
                    return super.instantiate(classLoader, className);
                }

                try {

                    Fragment fragment = fragmentClass.getDeclaredConstructor().newInstance();

                    for (LifecycleEventObserver observer : mLifecycleObservers) {
                        fragment.getLifecycle().addObserver(observer);
                    }

                    return fragment;

                } catch (ReflectiveOperationException cause) {
                    throw new RuntimeException(cause);
                }
            }
        };
    }
}

用途:

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;

import static org.junit.Assert.assertEquals;

import android.widget.TextView;

import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.testing.FragmentScenario;
import androidx.lifecycle.Lifecycle;
import androidx.test.ext.junit.runners.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.Objects;

import you.packagename.R;

@RunWith(AndroidJUnit4.class)
public class MyFragmentTest {
    @Rule
    public FragmentScenarioRule<MyFragment> init = new FragmentScenarioRule<>(MyFragment.class)
            .registerCallback(
                    Lifecycle.Event.ON_CREATE, // or another available event
                    fragment -> {
                        // do something you needed with the fragment instance here

                        // or

                        FragmentActivity activity = fragment.requireActivity();
                        // do something you needed with the fragment's host activity here
                    }
            );

    @Test
    public void testMyFragment() {
        FragmentScenario<MyFragment> scenario = init.getScenario();

        // any time you have access to the fragment instance in the following way
        scenario.onFragment(fragment -> {
            // call any methods of your fragment instance or/and test its state here
            fragment.onEvent(new MyEvent());
        });

        // also you have access to the fragment's views
        onView(withId(R.id.my_empty_text)).check(matches(isDisplayed()));
        onView(withId(R.id.my_recycler)).check(doesNotExist());

        // you can even move your fragment instance to the other state
        scenario.moveToState(Lifecycle.State.STARTED);
        // or recreate it
        scenario.recreate();

        // and check something after it too
        onView(withId(R.id.my_empty_text)).check(matches(withText("")));
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.