이전에 Presenter 테스트 작성으로 테스트의 중요성을 알아봤다.
https://seonghoonc.tistory.com/20
또한 MVP 를 MVVM으로 리팩터링하는 과정도 거쳤다.
https://seonghoonc.tistory.com/21
이제 ViewModel 테스트 작성해서 기능이 정상 동작하는지 알아보자.
먼저 이전과 똑같은 Counter class 를 가져왔다.
class Counter(initialCount: Int = DEFAULT_COUNT) {
private var count = initialCount
fun add(): Int {
return ++count
}
fun sub(): Int {
return --count
}
companion object {
const val DEFAULT_COUNT = 0
}
}
Domain Model Unit Test
먼저 Counter 도메인 모델 단위 테스트부터 작성하자.
Junit4 와 AssertJ 를 사용했다. Junit5 를 사용해도 된다.
class CounterTest {
@Test
fun `카운터가 0일 때 1 증가시키면 카운터는 1이다`() {
// given
val counter = Counter()
// when
val actual = counter.add()
// then
val expected = 1
assertThat(actual).isEqualTo(expected)
}
@Test
fun `카운터가 2일 때 1 감소시키면 카운터는 1이다`() {
// given
val counter = Counter(2)
// when
val actual = counter.sub()
// then
val expected = 1
assertThat(actual).isEqualTo(expected)
}
}
이제 Counter 를 사용한 ViewModel 을 테스트해보자.
MainViewModel
class MainViewModel : ViewModel() {
private val counter = Counter()
private val _count: MutableLiveData<Int> = MutableLiveData<Int>(0)
val count: LiveData<Int> = _count
fun plusCount() {
_count.value = counter.add()
}
fun minusCount() {
_count.value = counter.sub()
}
}
매우 간단한 구조의 ViewModel 이다.
이를 테스트하려면 어떻게 해야할까?
먼저 안드로이드 의존성을 가진 LiveData 테스트를 위해서 Rule 을 추가해야하며 나머지는 크게 다르지 않다.
testImplementation("androidx.arch.core:core-testing:2.2.0")
class MainViewModelTest {
private lateinit var vm: MainViewModel
@get:Rule
val instantExecutorRule = InstantTaskExecutorRule()
@Before
fun setUp() {
vm = MainViewModel()
}
@Test
fun `count 를 증가시키면 count 가 1 이다`() {
// given
// when
vm.plusCount()
// then
assertThat(vm.count.value).isEqualTo(1)
}
@Test
fun `count 를 감소시키면 count 가 -1 이다`() {
// given
// when
vm.minusCount()
// then
assertThat(vm.count.value).isEqualTo(-1)
}
}
그런데 실제 ViewModel 이라면 어떨까 repository 에 값을 요청하는 함수 등으로 비동기로 처리하고 있지 않을까?
그러면 어떻게 테스트 해야할까?
좀 더 복잡한 과정이 필요하다.
repository 패턴을 사용하기 때문에 목킹도 필요하다.
목킹에는 mockk 라이브러리를 사용
coroutine test 를 위해 implementation 추가
//mockk
testImplementation("io.mockk:mockk-android:1.13.5")
//coroutine test
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4")
...
private lateinit var vm: SomeViewModel
private lateinit var someRepository: SomeRepository
@get:Rule
val instantExecutorRule = InstantTaskExecutorRule()
@OptIn(ExperimentalCoroutinesApi::class)
@Before
fun setUp() {
Dispatchers.setMain(UnconfinedTestDispatcher())
someRepository = mockk()
vm = SomeViewModel(someRepository)
}
@OptIn(ExperimentalCoroutinesApi::class)
@After
fun finish() {
Dispatchers.resetMain()
}
이렇게 해서 비동기코드를 동기적으로 테스트 가능해졌다.
@Test
fun `어떤 것을 받아온다`() {
// given some ID 를 가지고 있다.
val someId = 1L
val fakeSomething = Something(someId, "베르", "최성훈")
coEvery { someRepository.loadSomething(any()) } returns fakeSomething
// when something 을 요청한다.
vm.loadSomething(someId)
// then something 을 받는다.
assertThat(vm.something.value).isEqualTo(fakeSomething)
}
이때 유의할 점은 코루틴을 사용하는 메서드를 목킹할때는 coEvery 를 사용해야 한다.
이렇게 하면 내가 정의한 ViewModel 이 정상 작동하는지 테스트로 확인할 수 있다!
MVVM 의 ViewModel 테스트가 MVP 의 Presenter 테스트보다 훨씬 간단한 구조를 띄어 가독성이 높았다!
'Android' 카테고리의 다른 글
[안드로이드] Flow 공식문서로 이해하기 - 2차 StateFlow & SharedFlow (0) | 2023.09.03 |
---|---|
[안드로이드] Flow 공식문서로 이해하기 - 1차 Flow 기초 (0) | 2023.08.25 |
[안드로이드] Fragment Lifecycle (프래그먼트 생명 주기) (0) | 2023.05.30 |
[안드로이드] Room Local DB (0) | 2023.05.22 |
[안드로이드] Fragment(프래그먼트) 와 Fragment Manager (0) | 2023.05.08 |