Espresso中IdlingResource详解

在ui测试中经常会遇到异步线程的操作(例如网络请求,后台计算等操作后验证界面元素),在uiautomator中一般会使用sleep方法来进行等待,在espresso中已经不推荐使用sleep这种形式来等待异步线程操作完成了。

IdlingResource

在espresso中,基于AsynTask的异步任务都会自动在测试中转化为同步线程,例如基于rxjava,rxandroid的异步网络请求,在等网络请求返回结果后Espresso才会执行下一步的操作,除了AsynTask外还有其他的异步操作,例如IntentService等,在这些场景下就需要用到IdlingResource了.

先贴一下idlingresource源码:

public interface IdlingResource
    {
    // 返回idlingresource名字,必须返回
    public String getName();
    // 主线程执行的方法,主要实现该方法
    public boolean isIdleNow();
    public void registerIdleTransitionCallback(ResourceCallback callback);
    public interface ResourceCallback {
    //回调方法,用于通知主线程是否从busy-idle
         public void onTransitionToIdle();
    }
}

要使IdlingResource生效,必须在测试时先进行注册,通过espresso.registerIdlingResources(yourIdlingResource);注册成功后,IdlingResource在运行时就会检查主线程的运行状态,espresso的IdlingResourceRegistry实现了onTransitionToIdle()的回调方法

private void registerToIdleCallback(final IdlingResource resource, final int position) {
     resource.registerIdleTransitionCallback(new ResourceCallback() {
         @Override
         public void onTransitionToIdle() {
              Message m = handler.obtainMessage(DYNAMIC_RESOURCE_HAS_IDLED);
              m.arg1 = position;
              m.obj = resource;
              handler.sendMessage(m);
         }
     });
}

IdlingResource在主线程中通过isIdleNow方法来判断主线程是否idle,若idle则回调onTransitionToIdle()方法告知espresso我已经处于idle状态,若false继续通过isIdleNow状态检测,直到超时或者回调onTransitionToidle()方法为止

附上自己写的一个小例子,场景是发送图片动态,界面出现发送成功后通知espresso执行下一步操作,其中textview由外部传入,通过activityTestRule的getActivity()方法获取当前activity并通过findViewById获取:

public class SendingIdlingResources implements IdlingResource{
     private TextView textView;
     private ResourceCallback mResourceCallback;
     public SendingIdlingResources(TextView textView){ this.textView=textView; }
     @Override
     public String getName() { return "sendingidlingresources"; }
     @Override public boolean isIdleNow() {
          String text = (String) textView.getText();
          String expectedString = "发送成功";
          boolean idle = (text.equals(expectedString));
          if (idle){
               System.out.println("发送成功:"+text);
               mResourceCallback.onTransitionToIdle();
          } else{
                 System.out.println("发送成功没有出现:"+text);
                }
          return idle;
      }
      @Override public void registerIdleTransitionCallback(ResourceCallback callback) {
                      this.mResourceCallback = callback;
       }
}

Espresso中RecyclerView操作

RecyclerViewActions
espresso中如果我们要对AdapterView进行操作,我们会使用onData()方法,然而在对RecyclerView使用onData()方法时会报错,因为RecyclerView是继承自ViewGroup而非AdapterView,如何对RecyclerView进行操作,google官方提供了对应的支持:

androidTestCompile ('com.android.support.test.espresso:espresso-contrib:2.2')

代码如下,点击recyclerview中的第五个item:

onView(withId(R.id.swipe_target)).perform(RecyclerViewActions.actionOnItemAtPosition(4,click()));

官方仅仅只对recyclerview提供了操作的api,如何check RecyclerView中的元素并没有提供。

RecyclerViewMatcher
官方没有提供,但是官方给出了建议,由自己实现自定义viewMathcer来匹配定位元素,github已有大神实现:
https://github.com/dannyroa/espresso-samples/tree/master/RecyclerView

public static RecyclerViewMatcher withRecyclerView(final int recyclerViewId) {
    return new RecyclerViewMatcher(recyclerViewId);
}
//检查recyclerview中第4个item是否含有内容为“hello”
onView(withRecyclerView(R.id.scroll_view).atPosition(3))
    .check(matches(hasDescendant(withText("hello"))));
// 点击该item
onView(withRecyclerView(R.id.scroll_view).atPosition(3)).perform(click());

对于屏幕外的元素
以上方法在对屏幕外的item操作时会报错,如何解决:

onView(withId(recyclerViewId)).perform(scrollToPosition(position));
onView(withRecyclerView(recyclerViewId).atPosition(position)).perform(click());

通过scroll方法首先滚动到屏幕外的元素,再对元素进行check或者操作即可。