このドキュメントでは、各種の一般的な Espresso テストのセットアップ方法について説明します。
隣あったビューによるビューのマッチング
レイアウトには、同一のビューを複数含めることができます。たとえば、連絡先の表に通話ボタンを繰り返し表示する場合、ビュー階層内のすべての通話ボタンに対して、同じ R.id
、同じテキスト、同じプロパティを指定できます。
以下のアクティビティの例では、テキスト "7"
を含むビューが複数の行にわたって繰り返されています。
このようなケースでは、同一のビューを隣りあった一意のラベル(たとえば、通話ボタンの横の連絡先名など)とペアにすることがよくあります。この例では、次のように hasSibling()
マッチャーを使用して選択対象を絞り込めます。
Kotlin
onView(allOf(withText("7"), hasSibling(withText("item: 0")))) .perform(click())
Java
onView(allOf(withText("7"), hasSibling(withText("item: 0")))) .perform(click());
アクションバー内のビューのマッチング
以下の ActionBarTestActivity
には 2 種類のアクションバーがあります。通常のアクションバーと、オプション メニューから作成されるコンテキスト アクションバーです。どちらのアクションバーにも、常に表示される項目が 1 つと、オーバーフロー メニューでのみ表示される項目が 2 つあります。ある項目をクリックすると、TextView がその項目のコンテンツに変わります。
両方のアクションバーに表示されたアイコンは、次のようなコード スニペットで簡単にマッチングできます。
Kotlin
fun testClickActionBarItem() { // We make sure the contextual action bar is hidden. onView(withId(R.id.hide_contextual_action_bar)) .perform(click()) // Click on the icon - we can find it by the r.Id. onView(withId(R.id.action_save)) .perform(click()) // Verify that we have really clicked on the icon // by checking the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("Save"))) }
Java
public void testClickActionBarItem() { // We make sure the contextual action bar is hidden. onView(withId(R.id.hide_contextual_action_bar)) .perform(click()); // Click on the icon - we can find it by the r.Id. onView(withId(R.id.action_save)) .perform(click()); // Verify that we have really clicked on the icon // by checking the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("Save"))); }
コンテキスト アクションバー用のコードも同様で、次のようになります。
Kotlin
fun testClickActionModeItem() { // Make sure we show the contextual action bar. onView(withId(R.id.show_contextual_action_bar)) .perform(click()) // Click on the icon. onView((withId(R.id.action_lock))) .perform(click()) // Verify that we have really clicked on the icon // by checking the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("Lock"))) }
Java
public void testClickActionModeItem() { // Make sure we show the contextual action bar. onView(withId(R.id.show_contextual_action_bar)) .perform(click()); // Click on the icon. onView((withId(R.id.action_lock))) .perform(click()); // Verify that we have really clicked on the icon // by checking the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("Lock"))); }
通常のアクションバーでオーバーフロー メニューの項目をクリックする際には少し注意が必要です。なぜなら、ハードウェア オーバーフロー メニューボタンがあるデバイスではオプション メニューのオーバーフロー項目が開くのに対し、ソフトウェア オーバーフロー メニューボタンがあるデバイスでは通常のオーバーフロー メニューが開くためです。さいわい Espresso では、その違いが自動的に処理されます。
通常のアクションバーの場合は、次のようにします。
Kotlin
fun testActionBarOverflow() { // Make sure we hide the contextual action bar. onView(withId(R.id.hide_contextual_action_bar)) .perform(click()) // Open the options menu OR open the overflow menu, depending on whether // the device has a hardware or software overflow menu button. openActionBarOverflowOrOptionsMenu( ApplicationProvider.getApplicationContext<Context>()) // Click the item. onView(withText("World")) .perform(click()) // Verify that we have really clicked on the icon by checking // the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("World"))) }
Java
public void testActionBarOverflow() { // Make sure we hide the contextual action bar. onView(withId(R.id.hide_contextual_action_bar)) .perform(click()); // Open the options menu OR open the overflow menu, depending on whether // the device has a hardware or software overflow menu button. openActionBarOverflowOrOptionsMenu( ApplicationProvider.getApplicationContext()); // Click the item. onView(withText("World")) .perform(click()); // Verify that we have really clicked on the icon by checking // the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("World"))); }
次の図は、ハードウェア オーバーフロー メニューボタンを持つデバイスでの表示を示しています。
コンテキスト アクションバーの場合も、次のように簡単に処理できます。
Kotlin
fun testActionModeOverflow() { // Show the contextual action bar. onView(withId(R.id.show_contextual_action_bar)) .perform(click()) // Open the overflow menu from contextual action mode. openContextualActionModeOverflowMenu() // Click on the item. onView(withText("Key")) .perform(click()) // Verify that we have really clicked on the icon by // checking the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("Key"))) } }
Java
public void testActionModeOverflow() { // Show the contextual action bar. onView(withId(R.id.show_contextual_action_bar)) .perform(click()); // Open the overflow menu from contextual action mode. openContextualActionModeOverflowMenu(); // Click on the item. onView(withText("Key")) .perform(click()); // Verify that we have really clicked on the icon by // checking the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("Key"))); } }
上記の例の完全なコードについては、GitHub にある ActionBarTest.java
サンプルをご覧ください。
ビューが表示されない場合のアサーション
一連のアクションを実行したら、テスト対象の UI の状態を確実にアサーションする必要があります。しかし、期待する結果が得られない(たとえば、何も起こらない)こともあります。ViewAssertions.matches()
を使用すれば、どのような Hamcrest ビュー マッチャーでも ViewAssertion
に変換できることを覚えておいてください。
以下の例では、標準の not()
マッチャーを使用して、isDisplayed()
マッチャーを反転させています。
Kotlin
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withId import org.hamcrest.Matchers.not onView(withId(R.id.bottom_left)) .check(matches(not(isDisplayed())))
Java
import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.assertion.ViewAssertions.matches; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static org.hamcrest.Matchers.not; onView(withId(R.id.bottom_left)) .check(matches(not(isDisplayed())));
ビューがビュー階層に存在している限り、この方法でうまくいきます。うまくいかない場合は、NoMatchingViewException
が発生するため、ViewAssertions.doesNotExist()
を使用する必要があります。
ビューが存在しない場合のアサーション
ビューがビュー階層に存在しなくなった場合(たとえば、あるアクションによって他のアクティビティに移動された場合)は、ViewAssertions.doesNotExist()
を使用する必要があります。
Kotlin
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.doesNotExist import androidx.test.espresso.matcher.ViewMatchers.withId onView(withId(R.id.bottom_left)) .check(doesNotExist())
Java
import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; import static androidx.test.espresso.matcher.ViewMatchers.withId; onView(withId(R.id.bottom_left)) .check(doesNotExist());
アダプターにデータ項目がない場合のアサーション
特定のデータ項目が AdapterView
内にないことを証明するには、少し異なる方法をとる必要があります。該当の AdapterView
を見つけて、それが保持するデータを調べなければなりません。onData()
の使用は不要です。代わりに、onView()
を使用して AdapterView
を見つけ出し、次に別のマッチャーを使用してビュー内のデータを調べます。
まず、次のようにしてマッチャーを準備します。
Kotlin
private fun withAdaptedData(dataMatcher: Matcher<Any>): Matcher<View> { return object : TypeSafeMatcher<View>() { override fun describeTo(description: Description) { description.appendText("with class name: ") dataMatcher.describeTo(description) } public override fun matchesSafely(view: View) : Boolean { if (view !is AdapterView<*>) { return false } val adapter = view.adapter for (i in 0 until adapter.count) { if (dataMatcher.matches(adapter.getItem(i))) { return true } } return false } } }
Java
private static Matcher<View> withAdaptedData(final Matcher<Object> dataMatcher) { return new TypeSafeMatcher<View>() { @Override public void describeTo(Description description) { description.appendText("with class name: "); dataMatcher.describeTo(description); } @Override public boolean matchesSafely(View view) { if (!(view instanceof AdapterView)) { return false; } @SuppressWarnings("rawtypes") Adapter adapter = ((AdapterView) view).getAdapter(); for (int i = 0; i < adapter.getCount(); i++) { if (dataMatcher.matches(adapter.getItem(i))) { return true; } } return false; } }; }
次に必要なのは、onView()
を使用して AdapterView
を見つけることです。
Kotlin
fun testDataItemNotInAdapter() { onView(withId(R.id.list)) .check(matches(not(withAdaptedData(withItemContent("item: 168"))))) } }
Java
@SuppressWarnings("unchecked") public void testDataItemNotInAdapter() { onView(withId(R.id.list)) .check(matches(not(withAdaptedData(withItemContent("item: 168"))))); } }
以上により、ID リストのアダプター ビューに「item: 168」と等しい項目が存在する場合に失敗するアサーションを用意できました。
完全なサンプルについては、GitHub にある AdapterViewTest.java
クラス内の testDataItemNotInAdapter()
メソッドをご覧ください。
カスタム障害ハンドラの使用
Espresso のデフォルトの FailureHandler
をカスタム ハンドラに置き換えると、エラー処理の追加や変更(スクリーンショットの取得や追加のデバッグ情報の受け渡しなど)が可能になります。
以下の CustomFailureHandlerTest
の例は、カスタム障害ハンドラの実装方法を示しています。
Kotlin
private class CustomFailureHandler(targetContext: Context) : FailureHandler { private val delegate: FailureHandler init { delegate = DefaultFailureHandler(targetContext) } override fun handle(error: Throwable, viewMatcher: Matcher<View>) { try { delegate.handle(error, viewMatcher) } catch (e: NoMatchingViewException) { throw MySpecialException(e) } } }
Java
private static class CustomFailureHandler implements FailureHandler { private final FailureHandler delegate; public CustomFailureHandler(Context targetContext) { delegate = new DefaultFailureHandler(targetContext); } @Override public void handle(Throwable error, Matcher<View> viewMatcher) { try { delegate.handle(error, viewMatcher); } catch (NoMatchingViewException e) { throw new MySpecialException(e); } } }
この障害ハンドラは、NoMatchingViewException
の代わりに MySpecialException
をスローし、他の障害はすべて DefaultFailureHandler
に委任します。CustomFailureHandler
は、このテストの setUp()
メソッドで Espresso に登録できます。
Kotlin
@Throws(Exception::class) override fun setUp() { super.setUp() getActivity() setFailureHandler(CustomFailureHandler( ApplicationProvider.getApplicationContext<Context>())) }
Java
@Override public void setUp() throws Exception { super.setUp(); getActivity(); setFailureHandler(new CustomFailureHandler( ApplicationProvider.getApplicationContext())); }
詳細については、FailureHandler
インターフェースと Espresso.setFailureHandler()
をご覧ください。
デフォルト以外のウィンドウを対象に指定する
Android では複数のウィンドウがサポートされます。通常は複数のウィンドウがユーザーとアプリ開発者に対して可視化されることはありませんが、状況によっては表示されることがあります(検索ウィジェットのメインアプリのウィンドウ上にオートコンプリート ウィンドウが描画される場合など)。デフォルトでは、Espresso は簡便化のためにヒューリスティックを使用して、テストで操作しようとしている Window
を推測します。このヒューリスティックはほとんどの場合適切に機能しますが、まれに操作対象のウィンドウを指定する必要が生じます。これを行うには、独自のルート ウィンドウ マッチャー(Root
マッチャー)を使用します。
Kotlin
onView(withText("South China Sea")) .inRoot(withDecorView(not(`is`(getActivity().getWindow().getDecorView())))) .perform(click())
Java
onView(withText("South China Sea")) .inRoot(withDecorView(not(is(getActivity().getWindow().getDecorView())))) .perform(click());
ViewMatchers
の場合と同様、RootMatchers
のセットがあらかじめ用意されています。もちろん、独自の Matcher
オブジェクトを実装することも可能です。
GitHub にある MultipleWindowTest サンプルをご覧ください。
リストビュー内のヘッダーまたはフッターのマッチング
ListViews
にヘッダーとフッターを追加するには、addHeaderView()
メソッドと addFooterView()
メソッドを使用します。マッチングするデータ オブジェクトを Espresso.onData()
で指定するには、addHeaderView()
と addFooterView()
の 2 番目のパラメータとして、事前設定済みのデータ オブジェクト値を渡してください。例:
Kotlin
const val FOOTER = "FOOTER" ... val footerView = layoutInflater.inflate(R.layout.list_item, listView, false) footerView.findViewById<TextView>(R.id.item_content).text = "count:" footerView.findViewById<TextView>(R.id.item_size).text = data.size.toString listView.addFooterView(footerView, FOOTER, true)
Java
public static final String FOOTER = "FOOTER"; ... View footerView = layoutInflater.inflate(R.layout.list_item, listView, false); footerView.findViewById<TextView>(R.id.item_content).setText("count:"); footerView.findViewById<TextView>(R.id.item_size).setText(String.valueOf(data.size())); listView.addFooterView(footerView, FOOTER, true);
その後、次のようにしてフッターのマッチャーを作成します。
Kotlin
import org.hamcrest.Matchers.allOf import org.hamcrest.Matchers.instanceOf import org.hamcrest.Matchers.`is` fun isFooter(): Matcher<Any> { return allOf(`is`(instanceOf(String::class.java)), `is`(LongListActivity.FOOTER)) }
Java
import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; @SuppressWarnings("unchecked") public static Matcher<Object> isFooter() { return allOf(is(instanceOf(String.class)), is(LongListActivity.FOOTER)); }
また、テストでのビューの読み込みは次のように簡単に行えます。
Kotlin
import androidx.test.espresso.Espresso.onData import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.sample.LongListMatchers.isFooter fun testClickFooter() { onData(isFooter()) .perform(click()) // ... }
Java
import static androidx.test.espresso.Espresso.onData; import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.sample.LongListMatchers.isFooter; public void testClickFooter() { onData(isFooter()) .perform(click()); // ... }
完全なコードサンプルについては、GitHub にある AdapterViewTest.java
の testClickFooter()
メソッドをご覧ください。