如何在片段中使用Espresso测试Recycler视图使用IdlingResource通过齐射获取数据

时间:2017-06-01 23:53:38

标签: android android-espresso android-testing

将,我有一个包含MainFragment的MainActivity, MainFragment实现了RecipeDataFetcher回调接口,该接口通过RecipeDataFetcher Class中的Volley / network获取数据。 MainFragment有一个Recyclerview,可以显示几张食谱卡

- 我需要在RecyclerView项目上执行测试

这是我的主要片段`公共类

MainFragment extends Fragment implements RecipesRecyclerAdapter.OnRecipeItemSelected,
        RecipesDataFetcher.RecipesFetcherDataListener {

    private View v;
    private RecipesDataFetcher recipesDataFetcher;
    private OnFragmentInteractionListener mListener;

private RecyclerView recyclerView;
private RecipesRecyclerAdapter recipesRecyclerAdapter;
private Recipes mRecipes;
private RecyclerView.LayoutManager layoutManager;


private SimpleIdlingResource mIdlingResource;


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    v = inflater.inflate(R.layout.fragment_main, container, false);
    mRecipes = new Recipes();
    recyclerView = (RecyclerView) v.findViewById(R.id.rv_recipes);
    if (mTwoPanel) {
        layoutManager = new GridLayoutManager(getActivity(), 2);
    } else {
        layoutManager = new LinearLayoutManager(getActivity());
    }

    recipesRecyclerAdapter = new RecipesRecyclerAdapter(this, mRecipes);
    recyclerView.setLayoutManager(layoutManager);
    recyclerView.setAdapter(recipesRecyclerAdapter);
    return v;
}

@VisibleForTesting
@NonNull
public SimpleIdlingResource getIdlingResource() {
    if (mIdlingResource == null) {
        mIdlingResource = new SimpleIdlingResource();
    }
    return mIdlingResource;
}



   @Override
    public void onSaveInstanceState(Bundle outState) {
//        super.onSaveInstanceState(outState);
    }
@Override
public void onAttach(Context context) {
    super.onAttach(context);
    if (context instanceof OnFragmentInteractionListener) {
        mListener = (OnFragmentInteractionListener) context;
    } else {
        throw new RuntimeException(context.toString()
                + " must implement OnFragmentInteractionListener");
    }
}

@Override
public void onDetach() {
    super.onDetach();
    mListener = null;
}

@Override
public void onItemSelected(int itemPosition) {
    mListener.onFragmentInteraction(mRecipes.get(itemPosition), mRecipes.size());
}

@Override
public void onResume() {
    super.onResume();
    if (mRecipes.isEmpty()) {
        getData();
    }
}

@Override
public void onConnectionFailure() {
    if (!checkOnlineState(getActivity())) {
        Logging.shortToast(getActivity(), getString(R.string.internet_error));
    } else {
        Logging.shortToast(getActivity(), getString(R.string.server_error));
    }
}

private void getData() {
    recipesDataFetcher = new RecipesDataFetcher(getActivity(), this);
    recipesDataFetcher.getRecipes(mIdlingResource);
}

@Override
public void onConnectionDone(Recipes recipes) {

    mRecipes.addAll(recipes);
    recipesRecyclerAdapter.notifyDataSetChanged();

    //TODO: Do bulk inset in a background thread and for once
    ContentValues[] contentValues = new ContentValues[recipes.size()];
    for (int i = 0; i < recipes.size(); i++) {
        ContentValues contentValue = new ContentValues();
        Gson gson = new Gson();
        String ingredients = gson.toJson(recipes.get(i).getIngredients());
        String steps = gson.toJson(recipes.get(i).getSteps());
        contentValue.put(COLUMN_RECIPE_INGREDIENTS, ingredients);
        contentValue.put(COLUMN_RECIPE_STEPS, steps);
        contentValue.put(COLUMN_RECIPE_SERVINGS, recipes.get(i).getServings());
        contentValue.put(COLUMN_RECIPE_IMAGE, recipes.get(i).getImage());
        contentValue.put(COLUMN_RECIPE_NAME, recipes.get(i).getName());
        contentValues[i] = contentValue;
    }

    getContext().getContentResolver().bulkInsert(CONTENT_URI, contentValues);

    if(mIdlingResource!=null){
        mIdlingResource.setIdleState(true);
    }

}


public interface OnFragmentInteractionListener {
    void onFragmentInteraction(Recipe recipe, int size);
}`

这是我的dataFetching类

public class RecipesDataFetcher extends BaseDataFetcher {
    public RecipesDataFetcher(Context context, BaseDataFetcherListener mListener) {
        super(context, mListener);
    }



    public void getRecipes(SimpleIdlingResource simpleIdlingResource) {
            if(simpleIdlingResource!=null){
            simpleIdlingResource.setIdleState(false);}
        String URL = BaseURL;
    Logging.log("getCountries: " + URL);
    JsonArrayRequest jsonObjReq = new JsonArrayRequest(URL, (JSONArray jsonArr) -> {
        Logging.log("getCountries response: " + jsonArr.toString());
        Recipes recipes = new Recipes(jsonArr);
        ((RecipesFetcherDataListener) mListener).onConnectionDone(recipes);
        if(simpleIdlingResource!=null){
            simpleIdlingResource.setIdleState(true);
        }
    }, this.errorListener);
    retryPolicy(jsonObjReq);
    getReQ().add(jsonObjReq);
}

public interface RecipesFetcherDataListener extends BaseDataFetcherListener {
    void onConnectionDone(Recipes recipes);
}

}

这是我的测试类

 @RunWith(AndroidJUnit4.class)
    public class MyFragmentTest {   

        private SimpleIdlingResource mIdlingResource;

    @Rule
    public FragmentTestRule<MainFragment> mFragmentTestRule = new FragmentTestRule<>(MainFragment.class);

    @Before
    public void registerIdlingResource() {
        // Launch the activity to make the fragment visible
        mFragmentTestRule.launchActivity(null);
         mIdlingResource = mFragmentTestRule.getFragment().getIdlingResource();
        Espresso.registerIdlingResources(mIdlingResource);
    }

    @Test
    public void fragment_can_be_instantiated() {


        // Then to test item in position 0
        onView(withId(R.id.rv_recipes))
                .perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));
    }

    //  unregister resources
    @After
    public void unregisterIdlingResource() {
        if (mIdlingResource != null) {
            Espresso.unregisterIdlingResources(mIdlingResource);
        }
    }
}

最后here是整个代码项目,请让我知道我到底做错了什么:D,我的帮助将受到高度赞赏。

1 个答案:

答案 0 :(得分:1)

错误说明

在您的存储库中浏览并运行 MyFragmentTest 测试类

我遇到了两个错误

  1. java.lang.RuntimeException:com.baking.www.baking.utilities.TestActivity@1d6e5b2必须实现OnFragmentInteractionListener
  2. 这是因为在 FragmentTestRule 类中,您扩展了ActivityTestRule&lt; TestActivity &gt;其中 TestActivity 没有实现OnFragmentInteractionListener所以解决这个问题的方法是你必须扩展已经实现了这个接口的 MainActivity ,所以这个类应该是这样的:

    public class FragmentTestRule<F extends Fragment> extends ActivityTestRule<MainActivity> {
    
    private final Class<F> mFragmentClass;
    private F mFragment;
    
    public FragmentTestRule(final Class<F> fragmentClass) {
        super(MainActivity.class, true, false);
        mFragmentClass = fragmentClass;
    }
    .....
    
    1. java.lang.NullPointerException:尝试调用虚方法&#39; com.baking.www.baking.IdlingResource.SimpleIdlingResource com.baking.www.baking.fragments.MainFragment.getIdlingResource()&#39;在空对象引用
    2. 这是因为您的 MyFragmentTest 在任何开始之前调用注释 @Before 导致你的片段尚未创建,使得这条线没有意义

       mIdlingResource = mFragmentTestRule.getFragment().getIdlingResource();
      

      所以在我看来: 不要使用碎片。活动更容易测试。您可以自己测试每个活动。在大多数情况下,碎片没有优于活动的优势。片段只会使实现和测试变得更加困难。

      使用排球运行空转资源的替代解决方案

      (a)在 MainActivity.java

      添加这些行

      @Nullable
      public SimpleIdlingResource mIdlingResource;
      
      @VisibleForTesting
      @NonNull
      public SimpleIdlingResource getIdlingResource() {
          if (mIdlingResource == null) {
              mIdlingResource = new SimpleIdlingResource();
          }
          return mIdlingResource;
      }
      

      并在 onCreate 中调用此方法         getIdlingResource(); 从idlingResouce获取实例。

      (b)在MainFragment.java

      • 删除类变量 mIdlingResource 及其getMethod
      • 您会发现 mIdlingResource
      • 的错误
      • 将任何 mIdlingResource 替换为((MainActivity)getActivity())。mIdlingResource

      (c)最后这是新的 MainActivityTest

      @RunWith(AndroidJUnit4.class)
      public class MainActivityTest {
      
      @Rule
      public ActivityTestRule<MainActivity> mActivityTestRule =
              new ActivityTestRule<>(MainActivity.class);
      
      private IdlingResource mIdlingResource;
      
      
      // Registers any resource that needs to be synchronized with Espresso before the test is run.
      @Before
      public void registerIdlingResource() {
          mIdlingResource = mActivityTestRule.getActivity().getIdlingResource();
          // To prove that the test fails, omit this call:
          Espresso.registerIdlingResources(mIdlingResource);
      }
      
      @Test
      public void idlingResourceTest() {
          onView(withId(R.id.rv_recipes)).check(matches(isDisplayed()));
          onView(withId(R.id.rv_recipes))
                  .perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));
      }
      
      // Remember to unregister resources when not needed to avoid malfunction.
      @After
      public void unregisterIdlingResource() {
          if (mIdlingResource != null) {
              Espresso.unregisterIdlingResources(mIdlingResource);
          }
      }
      
      }
      

      很抱歉,希望我帮忙:) :)