아래와 같이 UI테스트를 하던 중 스레드 딜레이를 쓰지 않았음에도 테스트가 27초까지 종료되지 않았고,
결과 화면에는 ComposeNotIdleException: Idling resource timed out: possibly due to compose being busy. 라는 에러가 났다.
스택오버플로우를 찾아보면 대부분 애니메이션(Lottie 등)이 있는 컴포넌트, mutableStateOf()가 쓰인 컴포넌트 등...
에러 글귀 그대로 Compose가 특정 작업을 완료하지 못하고 바쁜 상태에 머물러 있을 때 발생할 수 있는 것이다.
내가 실행한 테스트의 경우 애니메이션이 없는데 왜 발생하지...싶어서 테스트한 UI 범위 내의 Stateless하지 않은 컴포넌트 중에 remember{mutableStateOf()}를 사용한 게 있나 찾아봤다.
딱 한 가지가 걸렸다...ㅋㅋㅋ
실제 기기로 앱을 실행할 때는 문제 없이 '동작'하지만, 바람직하지 않은 상태관리 구조를 갖고 있는 컴포넌트였다. 컴포넌트 내부의 상태가 있기 때문에 상태 변경이 예상대로 이루어지지 않을 경우 Compose가 계속해서 UI를 재구성해서 타임아웃 오류가 발생한 것이다.
@Composable
fun ErrorUiSetView(onConfirmClick: () -> Unit, errorUiState: ErrorUiState, onCloseClick: () -> Unit) {
var isOpen by remember { mutableStateOf(true) } // <-!!!!!!!! 에러 원인 !!!!!!!!
val screenWidth = LocalConfiguration.current.screenWidthDp.dp
when (errorUiState) {
is ErrorUiState.ErrorData -> {
if (errorUiState.expiredTokenError) {
AppDesignDialog(
isOpen = isOpen,
modifier = Modifier.wrapContentHeight()
.width(screenWidth - 88.dp).semantics { testTag = "expiredTokenError" },
title = "리프레시 토큰이 만료되었습니다",
content = "다시 로그인해주세요",
buttonTitle = "로그인 하러가기",
onOkClick = {
isOpen = false
onConfirmClick()
},
onCloseClick = {
isOpen = false
onCloseClick()
}
)
}
...중략...
}
}
}
간단하게 아래 상태변수를 컴포넌트 밖으로 빼고 컴포넌트의 인자로 주입하는 방법, 즉 Stateless한 컴포넌트로 리팩토링하였다.
var isOpen by remember { mutableStateOf(true) }
테스트를 도입한지 얼마되지 않았다.그래서 바람직하지 않은 구조로 인해 테스트 에러를 마주한 건 이번이 처음이다🤩..이래서 테스트를 하면, 코드를 개선할 여지가 많아짐을 느낄 수 있었다.😘
unknownError가 발생했을 경우 로그인 요청 다이얼로그가 적절하게 뜨는지 테스트하기 위해 unknownError가 true인 errorUiState을 주입했다.
렌더링된 테스트 화면결과는 예상과 맞아떨어지지만 assertion에 실패했다고 뜬다. (하지만 내가 assert할 수 있다ㅋ)
@Test
fun test_unKnownError_ErrorUiSetViewExist() {
composeTestRule.setContent {
HbtiSurveyScreen(
onErrorHandleLoginAgain = {},
onBackClick = {},
onClickFinishSurvey = {},
viewModel = viewModel,
errorUiState = ErrorUiState.ErrorData(
expiredTokenError = false,
wrongTypeTokenError = false,
unknownError = true,
generalError = Pair(false, "원인불명 에러")
),
uiState = hbtiData
)
}
composeTestRule.onNodeWithTag("unknownError").assertIsDisplayed()
}