이제 코루틴 실행을 더 효과적으로 제어할 수 있는 Compose 테스트 API (createComposeRule, createAndroidComposeRule, runComposeUiTest, runAndroidComposeUiTest 등)의 v2 버전을 사용할 수 있습니다. 이 업데이트는 전체 API 노출 영역을 중복하지 않습니다. 테스트 환경을 설정하는 API만 업데이트되었습니다.
v1 API는 지원 중단되었으며 새 API로 마이그레이션하는 것이 좋습니다. 이전하면 테스트가 표준 코루틴 동작과 일치하는지 확인하고 향후 호환성 문제를 방지할 수 있습니다. 지원 중단된 v1 API 목록은 API 매핑을 참고하세요.
이러한 변경사항은 androidx.compose.ui:ui-test-junit4:1.11.0-alpha03+ 및 androidx.compose.ui:ui-test:1.11.0-alpha03+에 포함되어 있습니다.
v1 API는 UnconfinedTestDispatcher를 사용했지만 v2 API는 실행 중인 컴포지션에 기본적으로 StandardTestDispatcher를 사용합니다. 이 변경사항은 Compose 테스트 동작을 표준 runTest API와 정렬하고 코루틴 실행 순서를 명시적으로 제어합니다.
API 매핑
v2 API로 업그레이드할 때 일반적으로 찾기 + 바꾸기를 사용하여 패키지 가져오기를 업데이트하고 새로운 디스패처 변경사항을 적용할 수 있습니다.
또는 다음 프롬프트로 Gemini에 Compose 테스트 API v2로의 이전을 요청합니다.
AI 프롬프트
v1 테스트 API에서 v2 테스트 API로 이전
이 프롬프트는 이 가이드를 사용하여 v2 테스트 API로 이전합니다.
Migrate to Compose testing v2 APIs using the official
migration guide.다음 표를 사용하여 지원 중단된 v1 API를 v2 대체 API에 매핑합니다.
지원 중단됨 (v1) |
교체 (v2) |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
하위 호환성 및 예외
기존 v1 API는 이제 지원 중단되었지만 기존 동작을 유지하고 브레이킹 체인지를 방지하려면 UnconfinedTestDispatcher를 계속 사용하세요.
기본 동작이 변경된 유일한 예외는 다음과 같습니다.
AndroidComposeUiTestEnvironment 클래스에서 컴포지션을 실행하는 데 사용되는 기본 테스트 디스패처가 UnconfinedTestDispatcher에서 StandardTestDispatcher로 전환되었습니다. 이는 생성자를 사용하여 인스턴스를 만들거나 AndroidComposeUiTestEnvironment을 서브클래스로 지정하고 해당 생성자를 호출하는 경우에 영향을 미칩니다.
주요 변경사항: 코루틴 실행에 미치는 영향
API의 v1과 v2의 주요 차이점은 코루틴이 디스패치되는 방식입니다.
- v1 API (
UnconfinedTestDispatcher): 코루틴이 실행되면 현재 스레드에서 즉시 실행되어 다음 테스트 코드 줄이 실행되기 전에 완료되는 경우가 많았습니다. 프로덕션 동작과 달리 이렇게 즉시 실행하면 라이브 애플리케이션에서 발생할 수 있는 실제 타이밍 문제나 경합 상태가 실수로 마스킹될 수 있습니다. - v2 API (
StandardTestDispatcher): 코루틴이 실행되면 큐에 추가되고 테스트에서 가상 시계를 명시적으로 진행할 때까지 실행되지 않습니다. 표준 Compose 테스트 API (예:waitForIdle())는 이미 이 동기화를 처리하므로 이러한 표준 API를 사용하는 대부분의 테스트는 변경 없이 계속 작동해야 합니다.
일반적인 문제 및 해결 방법
v2로 업그레이드한 후 테스트가 실패하면 다음과 같은 패턴이 나타날 수 있습니다.
- 실패: 작업을 실행하지만 (예: ViewModel이 데이터를 로드함) 데이터가 아직 'Loading' 상태이므로 어설션이 즉시 실패합니다.
- 원인: v2 API를 사용하면 코루틴이 즉시 실행되지 않고 대기열에 추가됩니다. 결과를 확인하기 전에 작업이 대기열에 추가되었지만 실제로 실행되지는 않았습니다.
- 수정: 시간을 명시적으로 진행합니다. 작업을 실행할 시점을 v2 디스패처에 명시적으로 알려야 합니다.
이전 접근 방식
v1에서는 작업이 즉시 실행되고 완료되었습니다. v2에서는 loadData()가 아직 실제로 실행되지 않았기 때문에 다음 코드가 실패합니다.
// In v1, this launched and finished immediately.
viewModel.loadData()
// In v2, this fails because loadData() hasn't actually run yet!
assertEquals(Success, viewModel.state.value)
권장 방법
waitForIdle 또는 runOnIdle를 사용하여 대기열에 추가된 작업을 실행한 후 어설션을 실행합니다.
옵션 1: waitForIdle를 사용하면 UI가 유휴 상태가 될 때까지 시계가 진행되어 코루틴이 실행되었는지 확인할 수 있습니다.
viewModel.loadData()
// Explicitly run all queued tasks
composeTestRule.waitForIdle()
assertEquals(Success, viewModel.state.value)
옵션 2: runOnIdle를 사용하면 UI가 유휴 상태가 된 후 UI 스레드에서 코드 블록이 실행됩니다.
viewModel.loadData()
// Run the assertion after the UI is idle
composeTestRule.runOnIdle {
assertEquals(Success, viewModel.state.value)
}
수동 동기화
자동 진행이 사용 중지된 경우와 같이 수동 동기화가 포함된 시나리오에서는 테스트 시계가 일시중지되어 있으므로 코루틴을 실행해도 즉시 실행되지 않습니다. 가상 시계를 진행하지 않고 대기열에서 코루틴을 실행하려면 runCurrent() API를 사용하세요. 이렇게 하면 현재 가상 시간으로 예약된 작업이 실행됩니다.
composeTestRule.mainClock.scheduler.runCurrent()
UI가 안정화될 때까지 테스트 시계를 진행하는 waitForIdle()와 달리 runCurrent()는 현재 가상 시간을 유지하면서 대기 중인 작업을 실행합니다. 이 동작을 통해 시계가 유휴 상태로 진행되는 경우 건너뛰게 되는 중간 상태를 확인할 수 있습니다.
테스트 환경에서 사용되는 기본 테스트 스케줄러가 노출됩니다. 이 스케줄러는 Kotlin runTest API와 함께 사용하여 테스트 시계를 동기화할 수 있습니다.
runComposeUiTest로 마이그레이션
Kotlin runTest API와 함께 Compose 테스트 API를 사용하는 경우 runComposeUiTest로 전환하는 것이 좋습니다.
이전 접근 방식
createComposeRule를 runTest와 함께 사용하면 Compose용 시계와 테스트 코루틴 범위용 시계라는 두 개의 별도 시계가 생성됩니다. 이 구성으로 인해 테스트 스케줄러를 수동으로 동기화해야 할 수 있습니다.
@get:Rule val composeTestRule = createComposeRule() @Test fun testWithCoroutines() { composeTestRule.setContent { var status by remember { mutableStateOf("Loading...") } LaunchedEffect(Unit) { delay(1000) status = "Done!" } Text(text = status) } // NOT RECOMMENDED // Fails: runTest creates a new, separate scheduler. // Advancing time here does NOT advance the compose clock. // To fix this without migrating, you would need to share the scheduler // by passing 'composeTestRule.mainClock.scheduler' to runTest. runTest { composeTestRule.onNodeWithText("Loading...").assertIsDisplayed() advanceTimeBy(1000) composeTestRule.onNodeWithText("Done!").assertIsDisplayed() } }
권장 방법
runComposeUiTest API는 자체 runTest 범위 내에서 테스트 블록을 자동으로 실행합니다. 테스트 시계가 Compose 환경과 동기화되므로 더 이상 스케줄러를 수동으로 관리할 필요가 없습니다.
@Test fun testWithCoroutines() = runComposeUiTest { setContent { var status by remember { mutableStateOf("Loading...") } LaunchedEffect(Unit) { delay(1000) status = "Done!" } Text(text = status) } onNodeWithText("Loading...").assertIsDisplayed() mainClock.advanceTimeBy(1000 + 16 /* Frame buffer */) onNodeWithText("Done!").assertIsDisplayed() } }