EspressoのonDataで複数のAdapterViewをテスト

投稿日:  更新日:

EspressoのonDataはAdapterView(ListViewやSpinnerなど)が管理するアイテムのViewを検出します。

画面に複数のAdapterViewがある場合に、onDataでテストを行う方法を紹介します。単数の場合と違い、対象にするAdapterViewを指定する必要が出てきます。

スポンサーリンク

複数のAdapterViewがある場合

画面に複数のAdapterView(ListViewやSpinnerなど)が存在するアプリがあるとします。

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".SpiLisActivity">

    <LinearLayout
        android:id="@+id/layGroup"
        android:layout_width="280dp"
        android:orientation="horizontal">
        <ListView
            android:id="@+id/lisSimple"
            android:layout_height="wrap_content" />
        <Spinner
            android:id="@+id/spiArray"
            android:layout_height="wrap_content" />
    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>
KotlinJava
        val spiAdapter = ArrayAdapter(
                this,
                android.R.layout.simple_expandable_list_item_1,
                arrayOf("Hop", "Step", "Jump")
        )
        val _SpiArray: Spinner = findViewById(R.id.spiArray)
        _SpiArray.adapter = spiAdapter

        val lisAdapter = ArrayAdapter(
                this,
                android.R.layout.simple_expandable_list_item_1,
                arrayOf("one", "two", "three")
        )
        val _LisSimple: ListView = findViewById(R.id.lisSimple)
        _LisSimple.adapter = lisAdapter
        ArrayAdapter _SpiAdapter = new ArrayAdapter(
                this,
                android.R.layout.simple_expandable_list_item_1,
                new String[]{"Hop", "Step", "Jump"}
        );
        Spinner _SpiArray = findViewById(R.id.spiArray);
        _SpiArray.setAdapter(_SpiAdapter);

        ArrayAdapter _LisAdapter = new ArrayAdapter(
                this,
                android.R.layout.simple_expandable_list_item_1,
                new String[]{"one", "two", "three"}
        );
        ListView _LisSimple = findViewById(R.id.lisSimple);
        _LisSimple.setAdapter(_LisAdapter);

このアプリのAdapterViewをUIテストするために、次のようにonDataを使うとテストが失敗します。

KotlinJava
    @Test
    fun multi_test() {
        onData(`is`("two")).perform(click())
    }
    @Test
    public void multi_test() {
        onData(is("two")).perform(click());
    }

原因はonDataが対象のAdapterViewを判別できないためです。

テストのログに、その理由が出力されます。着目点は”****MATCHES****”の付いているViewです。ListViewとSpinnerに付いています。つまり、ListViewとSpinnerのどちらのAdapterViewを対象にするか、判別できないことを告げいるのです。

androidx.test.espresso.AmbiguousViewMatcherException: 'is assignable from class: 
    class android.widget.AdapterView' matches multiple views in the hierarchy.
Problem views are marked with '****MATCHES****' below.

View Hierarchy:
+>DecorView{id=-1, v=VISIBLE, child=2}
+->LinearLayout{id=-1, v=VISIBLE, child=2}
+-->ViewStub{id=16909288, res=action_mode_bar_stub, v=GONE, }
+-->FrameLayout{id=-1, v=VISIBLE, child=1}
+--->ActionBarOverlayLayout{id=2131165262, res=decor_content_parent, v=VISIBLE, child=2}
+---->ContentFrameLayout{id=16908290, res=content, v=VISIBLE, child=1}
+----->ConstraintLayout{id=-1, v=VISIBLE, child=1}
+------>LinearLayout{id=2131165286, res=layGroup, v=VISIBLE, child=2}
+------->ListView{id=2131165291, res=lisSimple, v=VISIBLE, child=3} ****MATCHES****
+-------->AppCompatTextView{id=16908308, res=text1, v=VISIBLE, text=one}
+-------->AppCompatTextView{id=16908308, res=text1, v=VISIBLE, text=two}
+-------->AppCompatTextView{id=16908308, res=text1, v=VISIBLE, text=three}
+------->AppCompatSpinner{id=2131165336, res=spiArray, v=VISIBLE, child=1} ****MATCHES****
+-------->AppCompatTextView{id=16908308, res=text1, v=VISIBLE, text=Hop}
+---->ActionBarContainer{id=2131165225, res=action_bar_container, v=VISIBLE, child=2}
+----->Toolbar{id=2131165223, res=action_bar, v=VISIBLE, child=2}
+------>AppCompatTextView{id=-1, v=VISIBLE, text=Espresso}
+------>ActionMenuView{id=-1, v=VISIBLE, child=0}
+----->ActionBarContextView{id=2131165231, res=action_context_bar, v=GONE, child=0}
+->View{id=16908335, res=statusBarBackground, v=VISIBLE, }

※上記のログは実際の出力と異なり、一部が省略されています。

これを解決するために、DataOptionsのinAdapterView(ViewMatcher)で、対象のAdapterViewを指定します。

KotlinJava
    @Test
    fun multi_test() {
        onData(`is`("two"))
                .inAdapterView(withId(R.id.lisSimple))
                .perform(click())
    }
    @Test
    public void multi_test() {
        onData(is("two"))
                .inAdapterView(withId(R.id.lisSimple))
                .perform(click());
    }

inAdapterView()の引数はViewMatcherで条件を指定する形になっています。もっとも有効な条件はwithId(リソースID)でしょう。

これで、onDataによるアイテムのViewの検出が可能になります。

スポンサーリンク

Spinnerは特殊(モーダル)

複数のAdapterViewがある場合でもSpinnerは特殊です。

SpinnerはDataOptionsのinAdapterView(ViewMatcher)が必要ありません。次のようにonDataを使うとテストは成功します。※アプリは前章と同じ

KotlinJava
    @Test
    fun spinner_test() {
        onView(withId(R.id.spiArray)).perform(click()) // Spinnerを開く
        onData(`is`("Jump")).perform(click())
    }
    @Test
    public void spinner_test() {
        onView(withId(R.id.spiArray)).perform(click()); // Spinnerを開く
        onData(is("Jump")).perform(click());
    }

SpinnerをUIテストするためには、onDataの前に閉じた状態のSpinnerを開かなければなりません。

Spinnerを開くとViewのツリー構造が変わります。モーダルウィンドウ的な処理が期待されているためであると思われます。

【Spinnerが閉じた状態のツリー構造】

View Hierarchy:
+>DecorView{id=-1, v=VISIBLE, child=2}
+->LinearLayout{id=-1, v=VISIBLE, child=2}
+-->ViewStub{id=16909288, res=action_mode_bar_stub, v=GONE, }
+-->FrameLayout{id=-1, v=VISIBLE, child=1}
+--->ActionBarOverlayLayout{id=2131165262, res=decor_content_parent, v=VISIBLE, child=2}
+---->ContentFrameLayout{id=16908290, res=content, v=VISIBLE, child=1}
+----->ConstraintLayout{id=-1, v=VISIBLE, child=1}
+------>LinearLayout{id=2131165286, res=layGroup, v=VISIBLE, child=2}
+------->ListView{id=2131165291, res=lisSimple, v=VISIBLE, child=3}
+-------->AppCompatTextView{id=16908308, res=text1, v=VISIBLE, text=one}
+-------->AppCompatTextView{id=16908308, res=text1, v=VISIBLE, text=two}
+-------->AppCompatTextView{id=16908308, res=text1, v=VISIBLE, text=three}
+------->AppCompatSpinner{id=2131165336, res=spiArray, v=VISIBLE, child=1}
+-------->AppCompatTextView{id=16908308, res=text1, v=VISIBLE, text=Hop}
+---->ActionBarContainer{id=2131165225, res=action_bar_container, v=VISIBLE, child=2}
+----->Toolbar{id=2131165223, res=action_bar, v=VISIBLE, child=2}
+------>AppCompatTextView{id=-1, v=VISIBLE, text=Espresso}
+------>ActionMenuView{id=-1, v=VISIBLE, child=0}
+----->ActionBarContextView{id=2131165231, res=action_context_bar, v=GONE, child=0}
+->View{id=16908335, res=statusBarBackground, v=VISIBLE, }

【Spinnerが開いた状態のツリー構造】

View Hierarchy:
+>PopupDecorView{id=-1, v=VISIBLE, child=1}
+->PopupBackgroundView{id=-1, v=VISIBLE, child=1}
+-->DropDownListView{id=-1, v=VISIBLE, child=3}
+--->AppCompatTextView{id=16908308, res=text1, v=VISIBLE, text=Hop}
+--->AppCompatTextView{id=16908308, res=text1, v=VISIBLE, text=Step}
+--->AppCompatTextView{id=16908308, res=text1, v=VISIBLE, text=Jump}
モーダルウィンドウ(modal window)とは
モーダルウィンドウとはユーザインターフェイスの手法の一つです。

開いたウィンドウを適切に操作するか、キャンセルするか、どちらかの操作を行わない限り、制御をもとのプロセスに戻さないものをいいます。

Spinnerが開かれると、そこに存在するAdapterViewはSpinner(DropDownListView)のみになるので、inAdapterViewは必要なくなるのです。

スポンサーリンク
スポンサーリンク