【发布时间】: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