EspressoテストでTextInputLayoutのエラーを確認したい


#TextInputLayoutのエラー表示を確認する
EditText用のエラーテキスト内容を確認するMatcherは存在するが、InputTextLayout用がなかったので
カスタムMatcherをwithTextのMatcherを参考に作成した
エラーかどうか、エラーのテキストを文字列、または文字列リソースでマッチングできるようにしている

```java:
    public static Matcher<View> withErrorText(String text) {
        return withErrorText(is(text));
    }


    public static Matcher<View> withErrorText(final Matcher<String> stringMatcher) {
        return new BoundedMatcher<View, TextInputLayout>(TextInputLayout.class) {
            @Override
            public void describeTo(Description description) {
                description.appendText("with Error Text: ");
                stringMatcher.describeTo(description);
            }

            @Override
            public boolean matchesSafely(TextInputLayout textInputLayout) {
                CharSequence error = textInputLayout.getError();
                return error != null && stringMatcher.matches(error.toString());
            }
        };
    }

    public static Matcher<View> withErrorText(final int resourceId) {
        return new BoundedMatcher<View, TextInputLayout>(TextInputLayout.class) {
            private String resourceName = null;
            private String expectedText = null;

            @Override
            public void describeTo(Description description) {
                description.appendText("with string from resource id: ");
                description.appendValue(resourceId);
                if (null != resourceName) {
                    description.appendText("[");
                    description.appendText(resourceName);
                    description.appendText("]");
                }
                if (null != expectedText) {
                    description.appendText(" value: ");
                    description.appendText(expectedText);
                }
            }

            @Override
            public boolean matchesSafely(TextInputLayout textInputLayout) {
                if (null == expectedText) {
                    try {
                        expectedText = textInputLayout.getResources().getString(resourceId);
                        resourceName = textInputLayout.getResources().getResourceEntryName(resourceId);
                    } catch (Resources.NotFoundException ignored) {
                        // NOP
                    }
                }
                CharSequence actualText = null;

                actualText = textInputLayout.getError();


                if (null != expectedText && null != actualText) {
                    // FYI: actualText may not be string ... its just a char sequence convert to string.
                    return expectedText.equals(actualText.toString());
                } else {
                    return false;
                }
            }
        };
    }

    public static Matcher<View> hasError() {
        return new BoundedMatcher<View, TextInputLayout>(TextInputLayout.class) {
            @Override
            public void describeTo(Description description) {
                description.appendText("has Error");
            }

            @Override
            public boolean matchesSafely(TextInputLayout textInputLayout) {
                CharSequence error = textInputLayout.getError();
                return error != null;
            }
        };
    }
```

# 使用例
ログイン画面で同じ内容のエラー文言が並ぶ場合などで効果を発揮する
allOf(withId(),withText())でも確認できるが、カスタムmatcherの方がスッキリすると思った

```java:
    public void エラーテスト() {
        onView(withId(R.id.textInputLayout_mail)).check(matches(isDisplayed()));
        onView(withId(R.id.textInputLayout_mail)).check(matches(hasError()));
        onView(withId(R.id.textInputLayout_mail)).check(matches(withErrorText("入力してください")));
        onView(withId(R.id.textInputLayout_mail)).check(matches(withErrorText(R.string.error_message_required)));
        onView(withId(R.id.textInputLayout_password)).check(matches(not(hasError())));
    }
```
参考：
https://developer.android.com/reference/android/support/test/espresso/matcher/ViewMatchers.html?hl=ja