无法测试内部初始化值的方法



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

登录演示者测试

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)

和登录演示器中的此代码.class

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

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

注意

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

.concatMap { doAfterRequestOTP(it) }

创建 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
}

并将我的登录演示者测试更新为此...

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

完成,问题解决了!

相关内容

  • 没有找到相关文章

最新更新