1

I'm trying to test Retrofit api with Spek

It throws nullPointerException on the on{...} block

Associated stacktrace : https://pastebin.com/gy6dLtGg

Here’s my test class

@RunWith(JUnitPlatform::class)
class AccountCheckViewModelTest : Spek({

    include(RxSchedulersOverrideRule)

    val httpException = mock<HttpException> {
        on { code() }.thenReturn(400)
    }

    given(" account check view model") {
        var accountCheckRequest = mock<CheckExistingAccountRequest>()
        var accountCheckResponse = mock<CheckExistingAccountResponse>()
        var webService = mock<IAPICalls>()

        val accountCheckViewModel = spy(VMAccountCheck(webService))

        beforeEachTest {
            accountCheckRequest = mock<CheckExistingAccountRequest>() {
                on { email }.thenReturn("foo@mail")
            }

            accountCheckResponse = mock<CheckExistingAccountResponse>() {
                on { firstName }.thenReturn("foo")
                on { email }.thenReturn("foo@mail")
            }

            webService = mock<IAPICalls> {
                on { checkExistingAccount(accountCheckRequest) }.thenReturn(Flowable.just(accountCheckResponse))
            }
         }
        on("api success") {
            accountCheckViewModel.checkIfAccountExists(request = accountCheckRequest)

            it("should call live data with first name as foo") {
               verify(accountCheckViewModel, times(1)).updateLiveData(accountCheckResponse.firstName, accountCheckResponse.email, null)
            }
        }
    }
}

Here is my RxSchedulersOverrideSpek class

 class RxSchedulersOverrideSpek : Spek({

    beforeGroup {
        RxJavaPlugins.onIoScheduler(Schedulers.trampoline())
        RxJavaPlugins.onComputationScheduler(Schedulers.trampoline())
        RxJavaPlugins.onNewThreadScheduler(Schedulers.trampoline())
    }
})
silent_control
  • 592
  • 5
  • 10

2 Answers2

1

You should use memoized to properly setup test values. The problem is that accountCheckViewModel was initialized in Spek's discovery phase, the webService mock that was passed to accountCheckViewModel was the value at that point (which you didn't mock any of its method). beforeEachTest is run during the execution phase, you've re-assigned webService here to the proper mock but accountCheckViewModel still holds the previous value.

given(" account check view model") {
  val accountCheckRequest by memoized {
    mock<CheckExistingAccountRequest>() {
      on { email }.thenReturn("foo@mail")
    }
  }
  val accountCheckResponse by memoized {
    mock<CheckExistingAccountResponse>() {
      on { firstName }.thenReturn("foo")
      on { email }.thenReturn("foo@mail")
    }
  }
  val webService by memoized {
    mock<IAPICalls> {
      on { checkExistingAccount(accountCheckRequest) }.thenReturn(Flowable.just(accountCheckResponse))
    }
  }

  val accountCheckViewModel by memoized {
    spy(VMAccountCheck(webService))
  }

  on("api success") {
    accountCheckViewModel.checkIfAccountExists(request = accountCheckRequest)

    it("should call live data with first name as foo") {
      verify(accountCheckViewModel, times(1)).updateLiveData(accountCheckResponse.firstName, accountCheckResponse.email, null)
    }
  }
}
raniejade
  • 515
  • 5
  • 14
0

Assuming you're using RxJava2 with RxAndroid you should override the RxAndroid scheduler with Schedulers.trampoline(). This way all jobs that subscribes on trampoline() will be queued and executed one by one in the same thread.

RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }

Your RxSchedulersOverrideSpek.kt should look like this:

object RxSchedulersOverrideSpek : Spek({

    beforeGroup {
        RxJavaPlugins.onIoScheduler(Schedulers.trampoline())
        RxJavaPlugins.onComputationScheduler(Schedulers.trampoline())
        RxJavaPlugins.onNewThreadScheduler(Schedulers.trampoline())
        RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }
    }

    afterGroup {
        RxJavaPlugins.reset()
        RxAndroidPlugins.reset()
    }
})
Ryan Amaral
  • 4,059
  • 1
  • 41
  • 39