Note: 이 코드랩에서는 컴포즈 테스트의 기본적인 내용을 알려주지 않는다. 이를 알아보려면 컴포즈 레이아웃 테스트 문서 또는 Testing in Jetpack Compose Codelab을 참조하자. TestNavHostController 사용과 같은 네비게이션 코드의 고급 테스트에 대해 자세히 알아보려면 Test Navigation 문서를 참조하자.

이 코드랩의 시작부터 우리는 navController를 컴포저블에 직접 전달하지 않고 대신 콜백을 매개변수로 전달했다. 즉, 모든 컴포저블을 개별적으로 테스트할 수 있다. 그러나 전체 NavHost를 테스트할 수도 있고, 이것이 여기서 다루고자 하는 내용의 전부다. 개별 컴포저블 함수를 테스트하려면, Testing in Jetpack Compose 코드랩에서 테스트하는 방법을 확인하자.

테스트 클래스 준비하기

NavHost는 Activity 자체와 별도로 테스트할 수 있다.

이 테스트는 여전히 Android 기기에서 실행되므로 /app/src/androidTest/java/com/example/compose/rally 아래의 androidTest 디렉토리에서 테스트 파일을 생성해야 한다.

테스트 파일을 생성하고, 이름을 RallyNavHostTest로 지정한다.

그런 다음, 컴포즈 테스트 API를 사용하기 위해 아래와 같이 컴포즈 테스트 규칙을 생성한다.

class RallyNavHostTest {

    @get:Rule
    val composeTestRule = createComposeRule()

}

이제 실제 테스트를 작성할 준비가 되었다.

첫번째 테스트 코드 작성해보기

@Test로 애노테이션을 달고 public 테스트 함수를 만든다. 해당 함수에서 테스트할 내용을 설정해야 한다. composeTestRule의 setContent를 사용하여 이 작업을 수행한다. Composable 매개변수를 사용하여 일반 앱에 있는 것처럼 Compose 코드를 작성할 수 있다. RallyActivity에서 했던 것처럼 RallyNavHost를 설정한다.

class RallyNavHostTest {

    @get:Rule
    val composeTestRule = createComposeRule()
    lateinit var navController: NavHostController

    @Test
    fun rallyNavHost() {
        composeTestRule.setContent {
            navController = rememberNavController()
            RallyNavHost(navController = navController)
        }
        fail()
    }
}

위의 코드를 복사한 경우, fail() 호출은 실제 assert(…)를 만들때까지 테스트가 실패하도록 한다. 이는 테스트 구현을 완료하라는 알림 역할을 한다.

Note: NavHost가 올바르게 작동하는지 확인하려면, 먼저 계층 구조를 만들어야 한다. 이는 asssertion이 setContent 함수 외부에서 작성되어야 함을 의미한다.

content description(콘텐츠 설명)을 통해 올바른 화면이 표시되는지 확인할 수 있다.
이 코드랩에서는 테스트 검증에 사용할 수 있도록 “Account 화면” 및 “Overview 화면”에 대한 content description이 제공된다. 테스트 클래스 자체에 lateinit 속성을 생성하여, 향후 테스트에서도 사용할 수 있다.

순조로운 시작을 위해, 일단 쉬운 것부터, OverviewScreen이 표시되는지 확인해보자.

class RallyNavHostTest {

    @get:Rule
    val composeTestRule = createComposeRule()
    lateinit var navController: NavHostController

    @Test
    fun rallyNavHost() {
        composeTestRule.setContent {
            navController = rememberNavController()
            RallyNavHost(navController = navController)
        }
        composeTestRule
            .onNodeWithContentDescription("Overview Screen")
            .assertIsDisplayed()
    }
}

만약에 Android 12와 관련해서 android:exported를 명시하라는 오류가 나온다면 다음의 절차를 통해 해결해보자.

1. 앱 모듈 build.gradle의 dependencies에 androidx.test:core-ktx:1.4.0 버전 이상이 추가 되었는지 확인
2. dependencies에 선언된 Test 관련 아티팩트가 최신버전인지 확인
3. 위의 단계로 해결되지 않으면, 임시로 targetSdk를 Android11로 낮추어서 진행 (추천하지 않음)

당연한 이야기지만, fail() 호출을 제거하고 테스트를 다시 실행하면 통과한다.

다음 각 테스트에서, RallyNavHost는 동일한 방식으로 설정됩니다. 따라서 코드를 깨끗하게 유지하기 위해 @Before 애노테이션이 달린 함수로 이것을 추출할 수 있습니다.

class RallyNavHostTest {

    @get:Rule
    val composeTestRule = createComposeRule()
    lateinit var navController: NavHostController

    @Before
    fun setupRallyNavHost() {
        composeTestRule.setContent {
            navController = rememberNavController()
            RallyNavHost(navController = navController)
        }
    }

    @Test
    fun rallyNavHost() {
        composeTestRule
            .onNodeWithContentDescription("Overview Screen")
            .assertIsDisplayed()
    }
}

테스트에서 이동해보기

새 목적지로 연결되어야 하는 UI 요소를 클릭하거나 해당 경로 이름으로 탐색을 호출하는 등 다양한 방법으로 네비게이션 구현을 테스트할 수 있다.

UI 및 테스트 규칙을 통한 테스트

앱의 구현을 테스트하려면 UI를 클릭하는 것이 좋다. “Accounts Screen”으로 연결되는 “All Acoounts” 버튼을 클릭하는 테스트를 작성하고 올바른 화면이 표시되는지 확인하자.

@Test
fun rallyNavHost_navigateToAllAccounts_viaUI() {
    composeTestRule
        .onNodeWithContentDescription("All Accounts")
        .performClick()
    composeTestRule
        .onNodeWithContentDescription("Accounts Screen")
        .assertIsDisplayed()
}

UI 및 navController를 통한 테스트

navController를 사용하여 assertion을 확인할 수도 있다. 이렇게 하려면 UI를 클릭한 다음 backstackEntry.value?.destination?.route를 사용하여 현재 경로를 예상한 경로와 비교해보자.

@Test
fun rallyNavHost_navigateToBills_viaUI() {
    // "All Bills"를 클릭할 때 
    composeTestRule.onNodeWithContentDescription("All Bills").apply {
        performScrollTo()
        performClick()
    }
    // 현재 경로가 "Bills"인지 확인
    val route = navController.currentBackStackEntry?.destination?.route
    assertEquals(route, "Bills")
}

navController를 통한 테스트

세 번째 옵션은 navController.navigate를 직접 호출하는 것이다. 여기에는 한 가지 주의 사항이 있다. UI 스레드에서 navController.navigate를 호출해야 한다. 메인 스레드 디스패처와 함께 코루틴을 사용하여 이를 수행할 수 있다. 그리고 새로운 상태에 대한 Assertion을 만들기 전에 호출이 발생해야 하므로, runBlocking 호출로 래핑해야 한다.

runBlocking {
    withContext(Dispatchers.Main) {
        navController.navigate(RallyScreen.Accounts.name)
    }
}

이를 통해 앱을 이동하고 경로가 예상한 곳으로 이동했는지 확인할 수 있다.

@Test
fun rallyNavHost_navigateToAllAccounts_callingNavigate() {
    runBlocking {
        withContext(Dispatchers.Main) {
            navController.navigate(RallyScreen.Accounts.name)
        }
    }
    composeTestRule
        .onNodeWithContentDescription("Accounts Screen")
        .assertIsDisplayed()
}

컴포즈에서 테스트하는 방법에 대해 자세히 알아보려면 다음의 코드랩을 확인해보자.

축하합니다.

Rally 앱에 네비게이션을 추가했으며, 이제 Jetpack Compose에서 네비게이션을 사용하는 주요 개념을 알게 되었다. 컴포저블 목적지의 네비게이션 그래프를 만들고, 경로에 인자를 추가하고, 딥 링크를 추가하고, 다양한 방법으로 구현을 테스트하는 방법을 배웠다.

카테고리: Compose

0개의 댓글

답글 남기기

Avatar placeholder

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.