RxKotlin UnitTest

오늘은 RxKotlin UnitTest 에 대해 알아 보자.

5가지 케이스로 나누었다. 다양한 케이스를 통해 어떤식으로 테스트를 진행하는지 알아보자.

1.countDownLatch

val latch = CountDownLatch(1)
service.getPosts()
   .subscribeOn(Schedulers.io())
   .observeOn(Schedulers.newThread()) //newThread 로 생성 했기 때문에 latch.await 을 사용 하지 않으면 onSuccess 타지 않고 바로 종료 됨.
   .subscribe(
       object : SingleObserver<List<Post>> {
           override fun onSuccess(t: List<Post>) {
               assert(t == posts)
               latch.countDown()
           }

           override fun onSubscribe(d: Disposable) {
               compositeDisposable.add(d)
           }

           override fun onError(e: Throwable) {
               latch.countDown()
           }
       }
   )

latch.await(3, TimeUnit.SECONDS) //CountDownLatch count 는 1 로 셋팅 되어서 countDown 이 한번 불리면 종료

2.blocking

val post = Post("포스트", false)
val posts = listOf(post)
Mockito.`when`(service.getPosts()).thenReturn(Single.create {
   it.onSuccess(posts)
})
//blockingGet은 완료 될 때 까지 대기. blockingGet은 내부에서 CountDownLatch 를 사용 한다.
val resultPosts = service.getPosts().blockingGet()
assert(resultPosts.size == 1)
assert(resultPosts == posts)

3.TestObserver

val post = Post("포스트", false)
val posts = listOf(post)
Mockito.`when`(service.getPosts()).thenReturn(Single.create {
   it.onSuccess(posts)
})
service.getPosts().test()
   .assertValueCount(1)
   .assertSubscribed()
   .assertNoErrors()
   .assertValues(posts)

4.Trampoline

@Before
fun setUp() {
  MockitoAnnotations.initMocks(this)
  //Schedulers.trampoline() 을 사용하면 현재 실행되고 있는 Thread 에서 실행 하게 해준다. immediate execution !!
  RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
  RxJavaPlugins.setComputationSchedulerHandler { Schedulers.trampoline() }
  RxJavaPlugins.setNewThreadSchedulerHandler { Schedulers.trampoline() }
  RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }
}

@Test
fun whenGetPosts_thenReturnSuccess() {
  val post = Post("첫번째 포스트", false)
  val post2 = Post("두번째 포스트", true)
  val posts = listOf(post, post2)
  Mockito.`when`(service.getPosts()).thenReturn(Single.create {
      it.onSuccess(posts)
  })

   service.getPosts()
      .subscribeOn(Schedulers.newThread())
      .observeOn(Schedulers.newThread())
      .subscribe(
          object : SingleObserver<List<Post>> {
              override fun onSuccess(t: List<Post>) {
                  assert(t == posts)
              }

              override fun onSubscribe(d: Disposable) {
                  compositeDisposable.add(d)
              }

              override fun onError(e: Throwable) {

              }
          }
      )
}

5.ViewModel Unit Test

scheduler , service 파라미터를 가진 ViewModel
@Before
fun setUp() {
  MockitoAnnotations.initMocks(this)
  testScheduler = TestScheduler()
  val schedulerProvider = TestSchedulerProvider(testScheduler)
  viewModel = SchedulerParamViewModel(
      service,
      schedulerProvider
  )
}

@Test
fun test() {
  val post = Post("첫번째 포스트", false)
  val post2 = Post("두번째 포스트", false)
  val posts = listOf(post, post2)
  Mockito.`when`(service.getPosts()).thenReturn(Single.create {
      it.onSuccess(posts)
  })

  viewModel.getPosts()
  testScheduler.triggerActions() //ViewModel 생성 시 주입된 scheduler
  Assert.assertTrue(viewModel.items.value!!.isNotEmpty())
  Assert.assertTrue(viewModel.items.value == posts)
}
service 파라미터를 가진 ViewModel
@Before
fun setUp() {
  MockitoAnnotations.initMocks(this)
  RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
  RxJavaPlugins.setComputationSchedulerHandler { Schedulers.trampoline() }
  RxJavaPlugins.setNewThreadSchedulerHandler { Schedulers.trampoline() }
  RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }

  //Test 코드는 바로 뷰모델을 만들었지만 실제 사용하는 경우 AAC ViewModel 은 파라미터가 있을 경우 ViewModelProvider.Factory 를 Custom 해서 만들면 된다.
  viewModel = GeneralViewModel(service)
}

@Test
fun test() {
  val post = Post("첫번째 포스트", false)
  val post2 = Post("두번째 포스트", false)
  val posts = listOf(post, post2)
  Mockito.`when`(service.getPosts()).thenReturn(Single.create {
     it.onSuccess(posts)
  })

   //getPost 내부에서 scheduler 셋팅을 io , mainThread 로 하지만 @Before 에서 셋팅한 스케줄러 값을 따라간다. 그래서 문제 없이 돌아감
  viewModel.getPosts()

  Assert.assertTrue(viewModel.items.value!!.isNotEmpty())
  Assert.assertTrue(viewModel.items.value == posts)
}

테스트 샘플 프로젝트는 링크를 통해 확인 할 수 있다.