IT Bookstore 과제 #14 - 실행 중인 API 중지 (4)

화면 종료시 실행 중인 API를 중지하기 위해서는 CompositeDisposable 객체의 clear() 또는 dispose() 함수를 실행하는 것입니다. 이 객체는 BaseViewModel 클래스 안에 정의하였으며, 그것의 onClear() 함수를 오버라이드(override)하여 CompositeDisposable 객체의 clear() 또는 dispose() 함수를 호출시킵니다. 이를 코드로 나타내면 다음과 같습니다.

open class BaseViewModel : ViewModel() {

    protected val disposables = CompositeDisposable()

    // ...

    override fun onCleared() {
        disposables.dispose()
        super.onCleared()
    }
}

어제 작성한 테스트 코드가 실패하는 원인 그리고 해결

  • 테스트 대상 클래스, 필드, 메소드: BookSearchViewModel, disposables, onCleared
  • 테스트 클래스, 메소드: BookSearchViewModelTest, onCleared
  • 원인: disposables 필드는 private 멤버이며, 이에 접근하기 위해 reflection을 사용했으나, BookSearchViewModel 클래스로는 접근 불가
  • 해결: BookSearchViewModel 대신 BaseViewModel 클래스를 통해 disposables 멤버에 접근

수정한 테스트 코드

class BookSearchViewModelTest {

    // 라이브 데이터 테스트 하려면, 이 객체가 있어야 한다.
    // 참고 링크 - https://jeroenmols.com/blog/2019/01/17/livedatajunit5/
    @Rule
    @JvmField
    val instantExecutorRule = InstantTaskExecutorRule()

    lateinit var viewModel: BookSearchViewModel

    @Before
    fun setUp() {
        viewModel = BookSearchViewModel()
    }

    // ...

    @Test
    fun onCleared() {
        viewModel.searchBooks("android")
        Thread.sleep(Const.COMMON_DELAY_MILLISECONDS.toLong())

        BookSearchViewModel::class.java.declaredMethods.first {
            it.name == "onCleared"
        }?.let {
            it.isAccessible = true
            it.invoke(viewModel)
        }

        BaseViewModel::class.java.declaredFields.first {
            it.name == "disposables"
        }?.let {
            it.isAccessible = true

            val disposables = it.get(viewModel) as CompositeDisposable
            Assert.assertEquals(0, disposables.size())
        }
    }

}

마치며...

이번 단위 테스트를 통해 reflection을 사용해보았으나, 아직 낯설다는 느낌입니다. 이에 대해 좀 더 공부를 해볼 필요성을 느꼈습니다. 내일은 이에 대한 내용을 정리하고 샘플 코드를 만들어 보겠습니다.


지난 IT Bookstore 과제