Custom Matcher
지난 글 AndroidX Espresso Matcher 분석에서 Matcher가 어떻게 동작하는지 코드 분석을 했었습니다.
이번에는 Custom Matcher를 직접 구현해보겠습니다. 이해를 돕기 위해 TextView의 text를 비교하는 Custom Matcher를 구현해보겠습니다.
ViewMatchers.withText()와 동일한 기능을 합니다.
기본 프로젝트 코드는 GitHub에서 다운받아주세요. Android Espresso로 UI 테스트 (1) 글에서 사용한 샘플코드입니다.
구현
androidTest 폴더에 CustomMatcher.kt를 생성하고 아래 코드를 입력해주세요.
withText()의 인자로 String 또는 ResourceId가 전달될 수 있기 때문에,
두개를 각각 다르게 처리할 수 있도록 TextStringMatcher와 TextResourceMatcher 클래스를 생성하였습니다.
String의 경우 Matcher<String>으로 변환하고 이미 구현된 로직으로 처리하였고, ResourceId인 경우 String을 가져와 직접 equals()로 비교하도록 구현했습니다.
object CustomMatcher {
    fun withText(substring: String): Matcher<View> {
        return withText(`is`(substring))
    }
    fun withText(resourceId: Int): Matcher<View> {
        return TextResourceMatcher(resourceId)
    }
    private fun withText(stringMatcher: Matcher<String>): Matcher<View> {
        checkNotNull(stringMatcher)
        return TextStringMatcher(stringMatcher)
    }
    private class TextStringMatcher(private val stringMatcher: Matcher<String>)
            : BoundedMatcher<View, TextView>(TextView::class.java) {
        public override fun matchesSafely(view: TextView): Boolean {
            val text: String = view.text.toString()
            return text != null && stringMatcher.matches(text)
        }
        override fun describeTo(description: Description) {
            description.appendText("with text: ")
            stringMatcher.describeTo(description)
        }
    }
    private class TextResourceMatcher(private val resourceId: Int)
            : BoundedMatcher<View, TextView>(TextView::class.java) {
        private var expectedText: String? = null
        public override fun matchesSafely(view: TextView): Boolean {
            expectedText = view.resources.getString(resourceId)
            val actualText: String = view.text.toString()
            return actualText != null && actualText.equals(expectedText)
        }
        override fun describeTo(description: Description) {
            if (expectedText != null) {
                description.appendText("with text: $expectedText")
            }
        }
    }
}Custom Matcher가 잘 만들어졌는지 테스트 코드를 작성해보겠습니다. 아래 코드처럼 먼저 인자로 String을 직접 전달하였고, 그 다음에는 ResourceId를 인자로 전달해보았습니다.
테스트를 실행해보니 모두 패스하였습니다.
자잘한 버그가 있을지 모르겠지만, ViewMatchers.withText()와 동일한 기능을 하는 Custom Matcher가 완성되었습니다!
@RunWith(AndroidJUnit4::class)
@LargeTest
class CustomMatcherTest {
    @Rule
    @JvmField
    var activityRule = ActivityTestRule(LocaleActivity::class.java)
    @Test
    fun noCountryExtra() {
        activityRule.launchActivity(Intent())
        Espresso.onView(withId(R.id.tvLocale))
            .check(matches(CustomMatcher.withText("No country string")))
        Espresso.onView(withId(R.id.tvLocale))
            .check(matches(CustomMatcher.withText(R.string.no_country_extra)))
    }
}정리
AndroidX Espresso Matcher 분석에서 Matcher의 코드를 분석해보았고, 이번에는 Custom Matcher를 만들어보았습니다. 기본적인 Matcher는 이미 만들어져 있기 때문에, 추가로 만들 일이 별로 없을 것 같습니다. 에스프레소의 내부 구조를 이해하는데 도움이 되었으면 좋겠습니다.
참고
Related Posts
- Android 14 - 사진/동영상 파일, 일부 접근 권한 소개
- Android - adb push, pull로 파일 복사, 다운로드
- Android 14 - 암시적 인텐트 변경사항 및 문제 해결
- Jetpack Compose - Row와 Column
- Android 13, AOSP 오픈소스 다운로드 및 빌드
- Android 13 - 세분화된 미디어 파일 권한
- Android 13에서 Notification 권한 요청, 알림 띄우기
- Android 13에서 'Access blocked: ComponentInfo' 에러 해결
- 에러 해결: android gradle plugin requires java 11 to run. you are currently using java 1.8.
- 안드로이드 - 코루틴과 Retrofit으로 비동기 통신 예제
- 안드로이드 - 코루틴으로 URL 이미지 불러오기
- Android - 진동, Vibrator, VibrationEffect 예제
- Some problems were found with the configuration of task 에러 수정
- Query method parameters should either be a type that can be converted into a database column or a List
- 우분투에서 Android 12 오픈소스 다운로드 및 빌드
- Android - ViewModel을 생성하는 방법
- Android - Transformations.map(), switchMap() 차이점
- Android - Transformations.distinctUntilChanged() 소개
- Android - TabLayout 구현 방법 (+ ViewPager2)
- Android - 휴대폰 전화번호 가져오는 방법
- Android 12 - Splash Screens 알아보기
- Android 12 - Incremental Install (Play as you Download) 소개
- Android - adb 명령어로 bugreport 로그 파일 추출
- Android - adb 명령어로 App 데이터 삭제
- Android - adb 명령어로 앱 비활성화, 활성화
- Android - adb 명령어로 특정 패키지의 PID 찾기
- Android - adb 명령어로 퍼미션 Grant 또는 Revoke
- Android - adb 명령어로 apk 설치, 삭제
- Android - adb 명령어로 특정 패키지의 프로세스 종료
- Android - adb 명령어로 screen capture 저장
- Android - adb 명령어로 System 앱 삭제, 설치
- Android - adb 명령어로 settings value 확인, 변경
- Android 12 - IntentFilter의 exported 명시적 선언
- Android - adb 명령어로 공장초기화(Factory reset)
- Android - adb logcat 명령어로 로그 출력