无法测试内部初始化值

时间:2018-03-17 05:56:18

标签: android kotlin mockito junit4

在android中,我在我的presenter类中有连接到远程/本地数据的方法,我想测试它的行为。我的项目是MVP + Dagger + RxJava

我想测试requestAuthorization()

LoginPresenter

class LoginPresenter @Inject constructor(val appRepo: AppDataStore, var scheduler: SchedulerProvider) : LoginContract.Presenter {

    private val requireMobileLength = 10
    private var view: LoginContract.View? = null
    private val disposable = CompositeDisposable()

    override fun takeView(view: LoginContract.View) {
        this.view = view
    }

    override fun requestAuthorization(mobile: String) {
        if (checkIsNotValidMobile(mobile)) {
            view?.showErrorInvalidMobile()
        } else {
            view?.showLoadingDialog()
            disposable.clear()
            disposable.add(appRepo.getAuthorizeFromRemote(mobile)
                    .concatMap { doAfterLogin(it) }
                    .concatMap { doAfterSaveToken(it) }
                    .concatMap { doAfterGetUserProfile(it) }
                    .concatMap { doAfterSaveUserProfile(it, mobile) }
                    .concatMap { doAfterRequestOTP(it) }
                    .observeOn(scheduler.ui())
                    .subscribeOn(scheduler.io())
                    .subscribe({
                        view?.dismissLoadingDialog()
                        if (it.isSuccess) {
                            view?.navigateToVerifyOTP(mobile)
                        } else {
                            view?.showToast(it.message)
                        }
                    }, {
                        view?.dismissLoadingDialog()
                        view?.showToast(it.message)
                    }))
        }
    }


    private fun doAfterLogin(result: BaseDataResult<String>): Observable<BaseDataResult<String>> {
        if (result.isSuccess) {
            return appRepo.saveAuthorizeInLocal(result.data)
        } else {
            throw Exception(result.message)
        }
    }

    private fun doAfterSaveToken(result: BaseDataResult<*>): Observable<BaseDataResult<UserProfileResponse>> {
        if (result.isSuccess) {
            return appRepo.getUserProfileFromRemote()
        } else {
            throw Exception(result.message)
        }
    }

    /***
     * Show ignore error if data null or empty
     */
    private fun doAfterGetUserProfile(result: BaseDataResult<UserProfileResponse>): Observable<BaseDataResult<UserProfile>> {
        if (result.isSuccess) {
            return appRepo.saveUserProfileInLocal(result.data.customerId ?: "", result.data.shopId
                    ?: "")
        } else {
            throw Exception(result.message)
        }
    }

    private fun doAfterSaveUserProfile(result: BaseDataResult<*>, mobile: String): Observable<BaseDataResult<Any?>> {
        if (result.isSuccess) {
            return appRepo.requestOTPFromRemote(mobile)
        } else {
            throw Exception(result.message)
        }
    }

    private fun doAfterRequestOTP(result: BaseDataResult<*>): Observable<BaseDataResult<Long>> {
        if (result.isSuccess) {
            return appRepo.saveLastRequestOTPTimeInLocal(Date().time)
        } else {
            throw Exception(result.message)
        }
    }

    private fun checkIsNotValidMobile(mobile: String): Boolean {
        return mobile.isEmpty() || mobile.toIntOrNull() == null || mobile.length != requireMobileLength
    }

    override fun dropView() {
        disposable.clear()
        this.view = null
    }
}

LoginPresenterTest

class LoginPresenterTest {

    private val view: LoginContract.View = mock()
    private val api: AppDataStore = mock()
    private lateinit var presenter: LoginContract.Presenter
    private lateinit var testScheduler: TestScheduler

    @Before
    fun setup() {
        testScheduler = TestScheduler()
        val testSchedulerProvider = TestSchedulerProvider(testScheduler)
        presenter = LoginPresenter(api, testSchedulerProvider)
        presenter.takeView(view)
    }

    @Test
    fun requestAuthorization() {
        val mobile = "0911925225"
        val lastRequestOTPTime = Date().time
        val mockTokenResponse = BaseDataResult(true, "test-message", "test-token")
        val mockUserProfileResponse = BaseDataResult(true, "test-message", UserProfileResponse(null, null))
        val mockAnyResponse = BaseDataResult<Any?>(true, "test-message", null)
        val mockLastOTPTimeResponse = BaseDataResult(true, "test-message", lastRequestOTPTime)


        doReturn(Observable.just(mockTokenResponse))
                .`when`(api)
                .getAuthorizeFromRemote(mobile)

        doReturn(Observable.just(mockTokenResponse))
                .`when`(api)
                .saveAuthorizeInLocal(mockTokenResponse.data)

        doReturn(Observable.just(mockUserProfileResponse))
                .`when`(api)
                .getUserProfileFromRemote()

        doReturn(Observable.just(mockUserProfileResponse))
                .`when`(api)
                .saveUserProfileInLocal(mockUserProfileResponse.data.customerId
                        ?: "", mockUserProfileResponse.data.shopId ?: "")

        doReturn(Observable.just(mockAnyResponse))
                .`when`(api)
                .requestOTPFromRemote(mobile)

        doReturn(Observable.just(mockLastOTPTimeResponse))
                .`when`(api)
                .saveLastRequestOTPTimeInLocal(lastRequestOTPTime)

        presenter.requestAuthorization(mobile)

        testScheduler.triggerActions()

        verify(view).showLoadingDialog()
        verify(view).dismissLoadingDialog()
        verify(view).navigateToVerifyOTP(mobile)
    }
}

错误日志

Wanted but not invoked:
view.navigateToVerifyOTP("0911925225");
-> at com.apg.mobile.shop.ui.entrance.login.LoginPresenterTest.requestAuthorization(LoginPresenterTest.kt:75)

However, there were exactly 3 interactions with this mock:
view.showLoadingDialog();
-> at com.apg.mobile.shop.ui.entrance.login.LoginPresenter.requestAuthorization(LoginPresenter.kt:30)

view.dismissLoadingDialog();
-> at com.apg.mobile.shop.ui.entrance.login.LoginPresenter$requestAuthorization$7.accept(LoginPresenter.kt:48)

view.showToast(
    "The mapper returned a null ObservableSource"
);
-> at com.apg.mobile.shop.ui.entrance.login.LoginPresenter$requestAuthorization$7.accept(LoginPresenter.kt:49)

在我看到此消息错误后,我尝试调试测试并发现在测试方法中rxjava将此抛出于OnError ..

  

NullPointerException,映射器返回一个null ObservableSource

在调查代码之后,我认为问题应该在以下几个方面。

LoginPresenterTest.class

中的此代码
doReturn(Observable.just(mockLastOTPTimeResponse))
        .`when`(api)
        .saveLastRequestOTPTimeInLocal(lastRequestOTPTime)

此代码在 LoginPresenter.class

private fun doAfterRequestOTP(result: BaseDataResult<*>): Observable<BaseDataResult<Long>> {
    if (result.isSuccess) {
        return appRepo.saveLastRequestOTPTimeInLocal(Date().time)
    } else {
        throw Exception(result.message)
    }
}

我认为测试演示者类中的日期模拟数据不一样吗?解决这个问题的方法应该是什么?

注意

删除requestAuthorization()方法中的以下行make test pass !!

  

.concatMap {doAfterRequestOTP(it)}

1 个答案:

答案 0 :(得分:0)

创建ClockProvider接口..

interface ClockProvider {
    fun newDateTime(): Date
}

实现它以便在应用程序中使用

class AppClockProvider : ClockProvider {
    override fun newDateTime(): Date = Date()
}

然后,由dagger提供

@Module(includes = [(DataModule::class)])
class AppModule(val app: Application) {

    //... hide other implementation

    @Provides
    @Singleton
    fun provideClockProvider(): ClockProvider = AppClockProvider()
}

并注入所需的演示者

class LoginPresenter @Inject constructor(val appRepo: AppDataStore, var scheduler: SchedulerProvider, var clock: ClockProvider) : LoginContract.Presenter { }

之后,在演示者中更改以下代码...

private fun doAfterRequestOTP(result: BaseDataResult<*>): Observable<BaseDataResult<Long>> {
    if (result.isSuccess) {
        return appRepo.saveLastRequestOTPTimeInLocal(Date().time)
    } else {
        throw Exception(result.message)
    }
}

到此......

private fun doAfterRequestOTP(result: BaseDataResult<*>): Observable<BaseDataResult<Long>> {
    if (result.isSuccess) {
        return appRepo.saveLastRequestOTPTimeInLocal(clock.newDateTime().time)
    } else {
        throw Exception(result.message)
    }
}

最后,在测试包中创建TestClockProvider.class

class TestClockProvider(var date: Date) : ClockProvider {
    override fun newDateTime(): Date = date
}

并将我的LoginPresenterTest更新为此...

class LoginPresenterTest {

    private val view: LoginContract.View = mock()
    private val api: AppDataStore = mock()
    private lateinit var presenter: LoginContract.Presenter
    private lateinit var testScheduler: TestScheduler
    private lateinit var clockProvider: ClockProvider

    @Before
    fun setup() {
        testScheduler = TestScheduler()
        clockProvider = TestClockProvider(Date())
        val testSchedulerProvider = TestSchedulerProvider(testScheduler)
        presenter = LoginPresenter(api, testSchedulerProvider, clockProvider)
        presenter.takeView(view)
    }

    @Test
    fun requestAuthorization() {
        val mobile = "0911925225"
        val lastRequestOTPTime = clockProvider.newDateTime()
        val mockTokenResponse = BaseDataResult(true, "test-message", "test-token")
        val mockUserProfileResponse = BaseDataResult(true, "test-message", UserProfileResponse(null, null))
        val mockAnyResponse = BaseDataResult<Any?>(true, "test-message", null)
        val mockLastOTPTimeResponse = BaseDataResult(true, "test-message", lastRequestOTPTime)


        doReturn(Observable.just(mockTokenResponse))
                .`when`(api)
                .getAuthorizeFromRemote(mobile)

        doReturn(Observable.just(mockTokenResponse))
                .`when`(api)
                .saveAuthorizeInLocal(mockTokenResponse.data)

        doReturn(Observable.just(mockUserProfileResponse))
                .`when`(api)
                .getUserProfileFromRemote()

        doReturn(Observable.just(mockUserProfileResponse))
                .`when`(api)
                .saveUserProfileInLocal(mockUserProfileResponse.data.customerId
                        ?: "", mockUserProfileResponse.data.shopId ?: "")

        doReturn(Observable.just(mockAnyResponse))
                .`when`(api)
                .requestOTPFromRemote(mobile)

        doReturn(Observable.just(mockLastOTPTimeResponse))
                .`when`(api)
                .saveLastRequestOTPTimeInLocal(lastRequestOTPTime.time)

        presenter.requestAuthorization(mobile)

        testScheduler.triggerActions()

        inOrder(view).apply {
            verify(view).showLoadingDialog()
            verify(view).dismissLoadingDialog()
            verify(view).navigateToVerifyOTP(mobile)
            verifyNoMoreInteractions()
        }
    }
}

完成,问题解决了!!