지난 글에서 Hilt를 사용한 프레그먼트 테스트를 위한 환경과 테스트 방법을 간단하게 알아보았다. 이번 글에서는 동일하게 프레그먼트 테스트를 진행하는데 Navigation을 사용하여 프레그먼트 간 인터렉션에 관한 테스트를 정리한다.
이번 테스트는 Unit Test가 아니라 인터렉션에 대한 테스트가 필요하기에 Integration Test로 테스트를 작성한다.
테스트 프레그먼트 네이게이션
테스트를 위한 프레그먼트는 총 3개로 구성되어 있다.
- MarsPhotoFragment
- AddMarsPhotoItemFragment
- ImagePickFragment
각 프레그먼트는 버튼으로 프레그먼트 간 이동 인터렉션이 발생한다. 우리가 중점적으로 테스트 하려는 부분이 이 인터렉션 부분이다.
이 글에서 Navigation, ViewModel, Mockito, Espresso 등 상세한 사용법 및 설명은 생략한다.
1. 테스트 : MarsPhotoFragment -> AddMarsPhotoItemFragment (Floating Button)
우리가 첫 번째 테스트 하려는 사용자 인터렉션은 위의 프레그먼트에서 Floating Button을 클릭하는 것이다.
MarsPhotoFragment 코드
우리가 테스트 하려는 프레그먼트의 코드는 아래와 같다. 저 중 우리는 Floating action button을 클릭하여 다른 프레그먼트로 전환되는 과정에 대한 코드를 테스트할 것이다.
class MarsPhotoFragment : Fragment(R.layout.fragment_mars_photo) {
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.fabAddMarsPhotoItem.setOnClickListener {
findNavController().navigate(
R.id.action_marsPhotoFragment_to_addMarsPhotoFragment
)
}
}
}
테스트 코드
@MediumTest
@HiltAndroidTest
@ExperimentalCoroutinesApi
class MarsPhotoFragmentTest {
@get:Rule
var hiltRule = HiltAndroidRule(this)
@Before
fun setup() {
hiltRule.inject()
}
@Test
fun clickAddMarsPhotoItemButton_navigateToAddMarshPhotoItemFragment() {
val navController = mock(NavController::class.java)
launchFragmentInHiltContainer<MarsPhotoFragment> {
Navigation.setViewNavController(requireView(), navController)
}
//Click Floating action button
onView(withId(R.id.fabAddMarsPhotoItem)).perform(click())
Mockito.verify(navController).navigate(
R.id.action_marsPhotoFragment_to_addMarsPhotoFragment
)
}
}
테스트 코드 분석
테스트 코드를 상세하게 분석해본다.
HiltAndroidRule과 hiltRule.inject()는 앞서 설명하였다. HiltAndroidRule은 테스트 하려는 테스트 클래스에 추가해야 한다. 해당 룰은 컴포넌트의 상태를 관리하고, 테스트에 의존성을 주입하는데 사용된다. 주입은 실행은 hiltRule.inject() 함수 호출로 이뤄진다.
@get:Rule
var hiltRule = HiltAndroidRule(this)
@Before
fun setup() {
hiltRule.inject()
}
Mockito 라이브러리를 활용하여 NavController 클래스를 Mockup한다. 테스트는 프레그먼트에서 버튼 클릭 시 Navigation으로 다른 프레그먼트로 전환이 이뤄져야 하는데, 실제 전환을 이뤄지진 않고, 이뤄지는 것처럼 테스트하기 위해 MockUp을 사용한다. Mock에 대해 잘 모른다면 테스트 더블에 대해 정리한 글을 참고하자. (2022.10.08 - [Android/Testing] - [Android/Testing] #6. 테스트 시작 전에 테스트 더블(Test Double) 이해하기)
다음은 launchFragmentInHiltContainer는 앞선 글에서 설명하였고, 코드 또한 확인하였다. 해당 글을 참고하자. (2022.11.13 - [Android/Testing] - [Android/Testing] #9. Dagger-Hilt 적용하여 프레그먼트 테스트하기)
@Test
fun clickAddMarsPhotoItemButton_navigateToAddMarshPhotoItemFragment() {
val navController = mock(NavController::class.java)
launchFragmentInHiltContainer<MarsPhotoFragment> {
Navigation.setViewNavController(requireView(), navController)
}
//Click Floating action button
onView(withId(R.id.fabAddMarsPhotoItem)).perform(click())
Mockito.verify(navController).navigate(
R.id.action_marsPhotoFragment_to_addMarsPhotoFragment
)
}
onView()는 Espresso 라이브러리에서 지원하는 함수로 뷰와의 진입점을 제공한다. 이 테스트 코드에서는 프레그먼트에서 R.id.fabAddMarsPhotoItem (Floating Action Button)의 진입점을 제공하게 된다.
onView(withId(R.id.fabAddMarsPhotoItem)).perform(click()) 는 버튼을 클릭하는 이벤트를 발생시키도록 한다. 해당 버튼 클릭 시 NavController에 의해 프레그먼트 전환이 이뤄지지만, 앞서 NavController는 Mock 객체로 생성되었기에 실제로 프레그먼트 전환이 이뤄지진 않는다.
Mockito.verify(navController).navigate(
R.id.action_marsPhotoFragment_to_addMarsPhotoFragment
)
마지막으로 Mockito 라이브러리의 verify를 이용하여 navigate가 호출되었는지 검증하기 위한 코드로 테스트를 마무리한다.
2. 테스트 : AddMarsPhotoItemFragment BackPressButton Test
테스트하려는 AddMarsPhotoItemFragment의 구현은 아래와 같다. 테스트 하려는 동작은 뒤로가기 버튼 클릭 시 NavController가 현재 프레그먼트를 pop 하여 이전 프레그먼트로 전환되는 동작이다.
class AddMarsPhotoItemFragment : Fragment(R.layout.fragment_add_mars_photo_item) {
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.ivMarsImage.setOnClickListener {
findNavController().navigate(R.id.action_addMarsPhotoFragment_to_imagePickFragment)
}
val callback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
viewModel.setCurImageUrl("")
findNavController().popBackStack()
}
}
requireActivity().onBackPressedDispatcher.addCallback(callback)
}
}
테스트 코드
@MediumTest
@HiltAndroidTest
@ExperimentalCoroutinesApi
class AddMarsPhotoItemFragmentTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@Before
fun setup() {
hiltRule.inject()
}
@Test
fun pressBackButton_popBackStack() {
val navController = mock(NavController::class.java)
launchFragmentInHiltContainer<AddMarsPhotoItemFragment>() {
Navigation.setViewNavController(requireView(), navController)
}
pressBack()
verify(navController).popBackStack()
}
}
Hilt, Mock 객체 생성, launchFragmentInHiltContainer은 설명하지 않는다.
위의 테스트 코드와 매우 유사하다. 여기서 다른점은 Espresso 라이브러리에서 제공하는 pressBack() 함수를 호출하는 것이다. (이 전 테스트에서는 onView()를 통해 버튼 클릭을 수행했다)
해당 버튼 클릭 시 NavContoller에 의해 popBackStack()이 발생하였는지 verify() 함수를 통해 검증한다.
정리
Navigation을 적용한 프레그먼트 테스트는 Espresso와 Mockito 라이브러리를 활용하여 손쉽게 테스트 하였다. 실제로 테스트 전에는 이 글에서 설명하지 않은 Espresso와 Mockito 라이브러리를 좀 더 심도있게 학습하는 것을 추천한다. (동작 원리 까지 파악한다면 더욱 좋을 것 같다.)
'Android > Testing' 카테고리의 다른 글
[Android/Testing] #9. Dagger-Hilt 적용하여 프레그먼트 테스트하기 (0) | 2022.11.13 |
---|---|
[Android/Testing] #8. 테스트에 Dagger-Hilt 적용해보기 (0) | 2022.11.08 |
[Android/Testing] #7. ViewModel 테스트하기 (with Fake Repository) (0) | 2022.10.22 |
[Android/Testing] #6. 테스트 시작 전에 테스트 더블(Test Double) 이해하기 (0) | 2022.10.08 |
[Android/Testing] #5. 테스트를 위한 기반 프로젝트 생성하기 (0) | 2022.10.02 |