【问题标题】:Unable to test method that internal initialize value无法测试内部初始化值的方法
【发布时间】:2018-03-17 05:56:18
【问题描述】:

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

我要测试requestAuthorization()

登录演示者

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)
    }
}

我认为 test 和 presenter 类中 Date 的模拟数据不一样,对吧?应该如何解决这个问题?

注意

删除requestAuthorization()方法中的以下行使测试通过!!

.concatMap { doAfterRequestOTP(it) }

【问题讨论】:

    标签: android kotlin mockito junit4


    【解决方案1】:

    创建 ClockProvider 接口..

    interface ClockProvider {
        fun newDateTime(): Date
    }
    

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

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

    那么,由匕首提供

    @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 { }
    

    之后,将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()
            }
        }
    }
    

    完成,问题解决!!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-09-19
      • 2010-11-28
      • 2021-05-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-08-03
      • 2011-05-21
      相关资源
      最近更新 更多