2013年12月1日日曜日

Espresso で Navigation Drawer を開閉する(DIを使わない編)

Navigation Drawer を開閉するには android.R.id.home ボタンをタップすればいいので、
  1. public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {  
  2.   
  3.     public MainActivityTest() {  
  4.         super(MainActivity.class);  
  5.     }  
  6.   
  7.     @Override  
  8.     public void setUp() throws Exception {  
  9.         super.setUp();  
  10.         // Espresso will not launch our activity for us, we must launch it via  
  11.         // getActivity().  
  12.         getActivity();  
  13.     }  
  14.   
  15.     public void testDrawerOpen() {  
  16.         // Drawer open  
  17.         onView(withId(android.R.id.home)).perform(click());  
  18.         // Click Settings  
  19.         onView(withId(R.id.settings)).perform(click());  
  20.     }  
  21. }  
で OK そうです(R.id.settings は Navigation Drawer 内のボタン)。

ところが、これはテストに失敗します。
onView(withId(R.id.settings)).perform(click());
のところで、そんな Id の View は見つからないと言われてしまいます。

原因は、Navigation Drawer が開き終わる前に View を探そうとするからです。

そこで、Navigation Drawer が開き(または閉じ)はじめてから、閉じる(または開く)まで、 Espresso に今は Idle 状態じゃないと伝えるようにします。

まず、IdlingResource インタフェースを実装したクラスのインスタンスを用意し、Espresso.registerIdlingResources()で登録します。

IdlingResource を実装したクラスとして、CountingIdlingResource が用意されています。
このクラスは内部でカウンターを持っていて、increment() と decrement() でカウンターの値を変え、カウンターが 0 のときが Idle 状態として Espresso に伝えられます。

Navigation Drawer を実現している DrawerLayout クラスの DrawerListener.onDrawerStateChanged() を利用して、カウンターの値を変えるようにします。

そのため、テスト対象の Activity に口を用意しないといけません。
  1. public class MainActivity extends ActionBarActivity implements DrawerListener {  
  2.   
  3.     private DrawerLayout mDrawerLayout;  
  4.     private View mDrawerContainer;  
  5.     private ActionBarDrawerToggle mDrawerToggle;  
  6.   
  7.     private DrawerFragment mDrawerFragment;  
  8.   
  9.     /** 
  10.      * Espresso で Drawer を開くため 
  11.      */  
  12.     public interface DrawerStateListener {  
  13.         public void onDrawerStateChanged(int newState);  
  14.     }  
  15.   
  16.     private DrawerStateListener mDrawerStateListener;  
  17.   
  18.     public void setDrawerListener(DrawerStateListener l) {  
  19.         mDrawerStateListener = l;  
  20.     }  
  21.   
  22.     public DrawerStateListener getDrawerListener() {  
  23.         return mDrawerStateListener;  
  24.     }  
  25.     /** 
  26.      *  
  27.      */  
  28.   
  29.     @Override  
  30.     protected void onCreate(Bundle savedInstance) {  
  31.         super.onCreate(savedInstance);  
  32.         setContentView(R.layout.activity_main);  
  33.   
  34.         mDrawerFragment = (DrawerFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_drawer);  
  35.   
  36.         mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);  
  37.         mDrawerContainer = findViewById(R.id.left_drawer);  
  38.   
  39.         mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, R.drawable.ic_drawer,  
  40.                 R.string.drawer_open, R.string.drawer_close) {  
  41.   
  42.             @Override  
  43.             public void onDrawerClosed(View drawerView) {  
  44.                 supportInvalidateOptionsMenu();  
  45.                 super.onDrawerClosed(drawerView);  
  46.             }  
  47.   
  48.             @Override  
  49.             public void onDrawerOpened(View drawerView) {  
  50.                 supportInvalidateOptionsMenu();  
  51.                 super.onDrawerOpened(drawerView);  
  52.             }  
  53.   
  54.             @Override  
  55.             public void onDrawerStateChanged(int newState) {  
  56.                 if (mDrawerStateListener != null) {  
  57.                     mDrawerStateListener.onDrawerStateChanged(newState);  
  58.                 }  
  59.                 super.onDrawerStateChanged(newState);  
  60.             }  
  61.         };  
  62.   
  63.         mDrawerLayout.setDrawerListener(mDrawerToggle);  
  64.   
  65.         getSupportActionBar().setDisplayHomeAsUpEnabled(true);  
  66.         getSupportActionBar().setHomeButtonEnabled(true);  
  67.     }  
  68.   
  69.     ...  
  70. }  
Activity に DrawerStateListener という口を用意しました。
この DrawerStateListener を実装した DrawerStateListenerImpl クラスを用意し、onDrawerStateChanged(int newState) で newState の値に応じて increment(), decrement() します。 このテストでは Navigation Drawer 部分をドラッグしないので DrawerLayout.STATE_DRAGGING は無いと思って簡略化しています。

setUp() の中で getActivity() で取得した Activity に対して DrawerStateListenerImpl のインスタンスを差し込み、registerIdlingResources() で DrawerStateListenerImpl で参照している CountingIdlingResource のインスタンスを登録しています。
  1. public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {  
  2.   
  3.     public MainActivityTest() {  
  4.         super(MainActivity.class);  
  5.     }  
  6.   
  7.     private class DrawerStateListenerImpl implements DrawerStateListener {  
  8.         private final DrawerStateListener realDrawerListener;  
  9.         private final CountingIdlingResource mIdlingResource;  
  10.   
  11.         private DrawerStateListenerImpl(DrawerStateListener l, CountingIdlingResource idlingResource) {  
  12.             this.realDrawerListener = l;  
  13.             this.mIdlingResource = checkNotNull(idlingResource);  
  14.         }  
  15.   
  16.         @Override  
  17.         public void onDrawerStateChanged(int newState) {  
  18.             // ドラッグしないので  
  19.             if (newState != DrawerLayout.STATE_IDLE) {  
  20.                 mIdlingResource.increment();  
  21.             } else {  
  22.                 mIdlingResource.decrement();  
  23.             }  
  24.   
  25.             if (realDrawerListener != null) {  
  26.                 realDrawerListener.onDrawerStateChanged(newState);  
  27.             }  
  28.         }  
  29.     }  
  30.   
  31.     @Override  
  32.     public void setUp() throws Exception {  
  33.         super.setUp();  
  34.         // Espresso will not launch our activity for us, we must launch it via  
  35.         // getActivity().  
  36.         MainActivity activity = getActivity();  
  37.         CountingIdlingResource countingResource = new CountingIdlingResource("DrawerCalls");  
  38.         activity.setDrawerListener(new DrawerStateListenerImpl(activity.getDrawerListener(), countingResource));  
  39.         registerIdlingResources(countingResource);  
  40.     }  
  41.   
  42.     public void testDrawerOpen() {  
  43.         // Drawer open  
  44.         onView(withId(android.R.id.home)).perform(click());  
  45.         // Click Settings  
  46.         onView(withId(R.id.settings)).perform(click());  
  47.     }  
  48. }  
このテストでは、ちゃんと R.id.settings ボタンがクリックされます。


0 件のコメント:

コメントを投稿