[Espresso] NavigationViewをNavigationViewActionsで操作してみようの話

AndroidTestでNavigationView操作したいよね？

ちょっと前に静かにReleaseされていた**Espresso 2.2.2にNavigationViewを操作するNavigationViewActions**というのが追加されていたのでそのお話。

NavigationViewの基本的な使い方は以下のQiitaにお任せするぜー。

**"NavigationViewを使ってGoogle Play風のメニューを作る【Android Design Support Library】"**
http://qiita.com/chuross/items/60af0aca3e1c3f946f09

## DrawerLayoutとNavigationViewは仲良しこよし

まず、NavigationViewの各メニューの特徴として、それぞれのメニューが個別のidを持っているってこと。

「あーならそのid指定してどれ選択するか書いてあげればいいのねー」ってことになる。

ただNavigationViewは基本的に**DrawerLayoutの子Viewとして追加して使うことが多い**と思ってる。

[DroidKaigiアプリのTop画面のレイアウト](https://github.com/konifar/droidkaigi2016/blob/master/app/src/main/res/layout/activity_main.xml)とか見るとまあわかると思う。以下のようにDrawerLayoutの子ViewとしてNavigationViewを使ってる。

```xml:github.com/konifar/droidkaigi2016/blob/master/app/src/main/res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context="io.github.droidkaigi.confsched.activity.MainActivity">

    <android.support.v4.widget.DrawerLayout
        android:id="@+id/drawer"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        tools:openDrawer="start">

        ....

        <android.support.design.widget.NavigationView
            android:id="@+id/nav_view"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_gravity="start"
            android:fitsSystemWindows="true"
            app:headerLayout="@layout/nav_header"
            app:itemTextColor="@color/nav_view_colors"
            app:menu="@menu/drawer_menu"
            app:insetForeground="#4000"/>
    </android.support.v4.widget.DrawerLayout>
</layout>
```

何が言いたいのかというと、NavigationViewをNavigationViewActionsで操作するには、**まずはDrawerLayoutをOpenしないとNavigationViewの各メニューがUI上表示されなくて操作できない**ってこと。

DrawerLayoutをOpenしないまま後ほど紹介するNavigationViewActionsを使うと以下のように
「Clickできないー(´・ω・｀)」ってエラーが出ます。

```java
android.support.test.espresso.PerformException: 
Error performing 'click on menu item with id' on view 'with id: 
io.github.droidkaigi.confsched.develop.debug:id/nav_view'.
```

## NavigationViewActionsの使い方

ではでは、以上のことをふまえてDroidKaigiアプリのNavigationViewの各メニューを操作してAndroidTestでそれぞれの画面を表示してみた。

まずは、build.gradleのdependenciesにEspresso2.2.2とかもろもそ追加します。

雑にdependencies部分だけ抜き出し。ちなみに、使ってるsupport libによってバージョンが違うぞ！とか怒られるので、Espressoに含まれてるものはexcludeで抜いておくのがいいよ。

```gradle:app/build.gradle
dependencies {
    compile "com.android.support:appcompat-v7:23.2.0"
    compile "com.android.support:design:23.2.0"

    androidTestCompile('com.android.support.test:runner:0.5') {
        exclude group: 'com.android.support', module: 'support-annotations'
    }
    androidTestCompile('com.android.support.test:rules:0.5') {
        exclude group: 'com.android.support', module: 'support-annotations'
    }
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2') {
        exclude group: 'com.android.support', module: 'support-annotations'
    }
    androidTestCompile('com.android.support.test.espresso:espresso-intents:2.2.2') {
        exclude group: 'com.android.support', module: 'support-annotations'
    }
    androidTestCompile ('com.android.support.test.espresso:espresso-contrib:2.2.2') {
        exclude group: 'com.android.support', module: 'support-annotations'
        exclude group: 'com.android.support', module: 'support-v4'
        exclude group: 'com.android.support', module: 'recyclerview-v7'
        exclude group: 'com.android.support', module: 'appcompat-v7'
        exclude group: 'com.android.support', module: 'design'
    }
}
```

テストコード側は以下のように**DrawerLayout Open -> NavigationViewActions.navigateTo(id)**を繰り返して各メニューを開いていく。

Thread.sleepは開いたのをちゃんと目視するためにメニューが切り替わったらちょっと止めてるだけ。

```java:MainActivityTest.java
@RunWith(AndroidJUnit4.class)
public class MainActivityTest {

    @Rule
    public IntentsTestRule<MainActivity> activityRule = new IntentsTestRule<>(MainActivity.class);

    @Test
    public void test_navigation_view() throws InterruptedException {
        onView(withId(R.id.drawer)).perform(DrawerActions.open());
        onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_my_schedule));
        Thread.sleep(1500);

        onView(withId(R.id.drawer)).perform(DrawerActions.open());
        onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_map));
        Thread.sleep(1500);

        onView(withId(R.id.drawer)).perform(DrawerActions.open());
        onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_settings));
        Thread.sleep(1500);

        onView(withId(R.id.drawer)).perform(DrawerActions.open());
        onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_sponsors));
        Thread.sleep(1500);

        onView(withId(R.id.drawer)).perform(DrawerActions.open());
        onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_about));
        Thread.sleep(1500);

        onView(withId(R.id.drawer)).perform(DrawerActions.open());
        onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_all_sessions));
        Thread.sleep(1500);
    }
}
```

基本は以下の二行です。

`DrawerActions.open`でDrawerLayoutを開くとUI上にNavigationViewの各メニューが表示されるので、`NavigationViewActions.navigateTo(id)`で選択したいメニューのidを引数に渡します。

```java
onView(withId(R.id.drawer)).perform(DrawerActions.open());
onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_all_sessions));
```


ちょっと注意して欲しいのは**DrawerLayoutをOpenする方法は[DrawerActions.open](http://developer.android.com/reference/android/support/test/espresso/contrib/DrawerActions.html#open())を使いのがよいです。**

昔は[DrawerActions.openDrawer](http://developer.android.com/reference/android/support/test/espresso/contrib/DrawerActions.html#openDrawer(int))ってものがあったんだけど、これはDeprecatedになってます。

```java
// Good!!
onView(withId(R.id.drawer)).perform(DrawerActions.open()); 

// Deprecated!! NO!!
DrawerActions.openDrawer(R.id.drawer);
```



端末上で動かすと以下のような感じでちゃんとDrawerLayoutをOpenしてNavigationViewの各メニューを選択して画面が切り替わってます。

![test.gif](https://raw.githubusercontent.com/operando/tmp/master/hammerheadMMB29Vshinobu03222016194953.gif)


## NavigationViewActionsの情報

以上のような感じでNavigationViewをNavigationViewActionsで操作できたわけですが、そもそもこれいつ追加されたんやー！！って話なんですよ。

んで、これを見つけたきっかけはちらっと以下のAndroid Testing Support LibraryのRelease Notesを見たのがきっかけ。

https://google.github.io/android-testing-support-library/downloads/release-notes/index.html

> **Espresso 2.2.2, Runner/Rules 0.5 (2016-02-22, silent release)**
> New Features

> * espresso
>  * Issue 194253: Add support for NavigationView in android support design library

ほむほむ。Espresso 2.2.2でNavigationViewの操作をSupportしたよ！！って感じ。

んじゃ、NavigationViewを操作するクラスなによ？？って話で、EspressoのJavadoc見てみた。

「Classes見れば追加されてるはずだよねー♪」

http://developer.android.com/reference/android/support/test/espresso/contrib/package-summary.html


![スクリーンショット 2016-03-22 18.25.27.png](https://qiita-image-store.s3.amazonaws.com/0/43637/5e707414-9e7a-bf09-22cf-0415294639e2.png)

...(´・ω・｀)

**お分かりいただけるだろうか**

なんとJavadocにNavigationViewActionsが追加されていない。

どうやら**Espresso 2.2.2, Runner/Rules 0.5 のRelease内容がJavadocに反映されていない。**

Silent Releaseだから？？それとも別に最新のJavadocあるの？？知っている人いたら教えてほしい...。

ローカルのjarとか見ればNavigationViewActionsの実装見れるけど面倒は人はNavigationViewActionsの中身をGistに移したので参考までに。

https://gist.github.com/operando/b10eb858fb7a341ce3de

ちなみに、NavigationViewActionsの実装は元々[android-testing](https://github.com/googlecodelabs/android-testing)のcustom actionとして実装されていたものをほぼほぼそのまま採用したっぽい実装になっている。

https://github.com/googlecodelabs/android-testing/blob/master/app/src/androidTest/java/com/example/android/testing/notes/custom/action/NavigationViewActions.java

## NavigationViewActions Sample

NavigationViewActionsを使った雑なSampleを作ったみたので手元で動かしたい人はこれで試してみてください。

https://github.com/operando/NavigationViewActionsSample

## まとめ

NavigationViewが簡単に操作できるようになってめでたしめでたしでした。