앱 통합 테스트를 진행하면서 한 가지 문제가 생겼다.
UI Test 를 진행하는 스크린 단에서는 MainThread 가 실행되는데, API 를 호출하고 처리하는 비즈니스 로직단에서는 ViewModelScope 를 호출하여 IO Thread 를 사용하는 것이다.
@Test
fun testMain(){
ComposeTestRule.onNodeWithText("API 호출").performClick()
ComposeTestRule.onNodeWithTag("API 수신 성공 버튼").assertIsEnabled()
}
@Composable
fun test(viewModel: MainViewModel) {
val boolean by viewModel.booleanStateFlow.collectAsStateWithLifecycle()
Column {
Text(
modifier = Modifier.clickable {
viewModel.setBoolean()
},
text = "API 호출"
)
Button(enabled = boolean, onClick = {}) {
Text(text = "API 수신 성공 버튼")
}
}
}
만약 위와 같은 테스트 코드가 있다고 치자.
테스트 순서 상 다음과 같은 플로우를 기대하였을 것이다
- 노드 트리에서 API 호출 텍스트를 가진 노드를 찾아 해당 컴포저블을 클릭처리.
- 1번 컴포저블을 클릭하였으므로 당연히 API 수신을 확인하는 버튼의 enabled 도 True 가 될 것을 기대
하지만 위와 같은 작업을 실행한다면 결과는 실패 처리가 된다.
왜냐하면 API 는 우리가 실행하는 Test Code 의 스코프 내에서 돌아가는 작업이 아닌, 별도의 ViewModel Scope 에서 돌아가는 환경이기 때문이다.
사실 이것에 대한 처리 방법은 생각보다 간단하다
ComposeTestRule 에서 사용 가능한 waitUntil 을 활용해주기만 하면 끝이난다.
설정해준 타임아웃 시간 내로 안의 결과값이 True 를 Return 할때까지 대기하는 함수
나의 경우 NavController.navigate 를 실행하였을때 NavController 가 현재 Route 를 벗어날 때까지 대기 상태로 만들도록 설정하였는데, API 수신의 경우 호출이 성공하였거나 실패하였을 때의 State 를 관리하는 객체를 이 WaitUntil 스코프 내에 등록 하면 자신이 원하는 상태가 되기 전까지 대기 상태로 만들어주므로, 결과가 Return 되지 않았음에도 다음 테스트 코드가 실행되는 불상사를 막을 수 있다.
LaunchedEffect 가 실행이 안될 때Android InstrumentTest 에서 UI Test 를 진행하다보면 분명 State 가 바뀜에 따라 LaunchedEffect 가 실행이 되어야하는데 실행이 안되는 경우가 존재할 것이다.
이럴 때는 ComposeTestRule.awaitIdle() 이나 ComposeTestRule.waitForIdle() 메소드를 활용하여 UI 가 안정적인 상태에 도달할 때까지 대기 상태로 만들어주면 정상적으로 실행되는 것을 확인할 수 있다.