我有以下RXJava 2.0代码:
private fun <T> wrapApiRequestSingle(apiCall: () -> Single<T>, token: Token) : Single<T> =
Single.defer {
apiCall.invoke()
}.retryWhen { obsError ->
obsError.flatMap<Single<T>> { error ->
when (error) {
is TokenExpiredException -> {
userRepository.getLoggedInUser().toFlowable().flatMap { userOptional ->
Publisher<Single<T>> {
if (userOptional.isPresent) {
mobileRemote.swapRefreshTokenForAccessToken(token.refreshToken, userOptional.get().emailAddress)
.onErrorResumeNext { refreshError ->
EventReporter.e(TAG, "Failed to refresh JWT.", refreshError)
tokenUseCases.deleteToken().andThen(preferences.singleOrError().flatMap { prefs ->
prefs.apply {
this.pushRegistrationId = ""
this.token = null
}.apply()
Single.error<Token>(NoLoggedInUserException())
})
}
} else {
EventReporter.e(TAG, "No user was logged in.", error)
tokenUseCases.deleteToken().andThen(preferences.singleOrError().flatMap { prefs ->
prefs.apply {
this.pushRegistrationId = ""
this.token = null
}.apply()
Single.error<Token>(NoLoggedInUserException())
})
}
}
}
}
else -> {
Flowable.error(error)
}
}
}
}
想法是,所有API调用都将被该函数包装。该函数有4条主要的执行路径:
TokenExpiredException
,调用失败,并且仅在有登录用户的情况下,代码才尝试刷新。刷新成功,并再次进行原始调用。TokenExpiredException
,调用失败,并且仅在有登录用户的情况下,代码才尝试刷新。如果刷新失败,则删除一些本地数据并返回包含Single
的{{1}}。NoLoggedInUserException
的{{1}}。代码已编译,我已经阅读了所有正在使用的功能的文档,但是对于第4种情况,运行时无法返回Single
。
我决定编写一个测试用例来测试第四条路径,而无需使用实际的API或使用任何实际的服务。这是我的测试代码(它使用Mockito来模拟各种子系统,例如NoLoggedInUserException
和Single.error(NoLoggedInUserException)
:
mobileRemote
这样的想法是,只要我的API调用返回一个tokenUseCases
,就会命中/**
* Set of tests to test the main presenter
*/
class ResourceInteractorTests : RobolectricTestBase() {
@Mock
private lateinit var injector: InjectorProvider
@Mock
private lateinit var preferences: Preferences
@Mock
private lateinit var userRepository: UserStorage
@Mock
private lateinit var tokenUseCases: TokenUseCases
@Mock
private lateinit var mobileRemote: MobileRemote
@Before
fun setup() {
// Initialize all the mocks in this class
MockitoAnnotations.initMocks(this)
whenever(this.injector.providePreferences()).thenReturn(Observable.just(preferences))
whenever(this.injector.provideUserStorage()).thenReturn(userRepository)
whenever(this.injector.provideTokenUseCases()).thenReturn(tokenUseCases)
whenever(this.injector.provideMobileRemote()).thenReturn(mobileRemote)
}
/**
* Test that getLocations ultimately propagates a [NoLoggedInUserException]
* When the remote call returns a [TokenExpiredException] and there is no logged in user
*/
@Test
fun onGetLocationsFailTokenExpiredNoLoggedInUser() {
// ARRANGE
whenever(this.tokenUseCases.getToken()).thenReturn(Single.just(Token("", Date(), "")))
whenever(this.mobileRemote.getLocations("")).thenReturn(Single.error(TokenExpiredException()))
whenever(this.userRepository.getLoggedInUser()).thenReturn(Single.just(Optional.absent()))
whenever(this.tokenUseCases.deleteToken()).thenReturn(Completable.complete())
val interactor = ResourceInteractor(this.injector)
// ACT
val shouldBeError = interactor.getLocations().test()
shouldBeError.awaitTerminalEvent(3, TimeUnit.SECONDS)
// ASSERT
shouldBeError.assertError { it is NoLoggedInUserException }
}
}
块(这是因为我在代码中放置了断点以进行验证)。然后,模拟的TokenExpiredException
返回retryWhen
,使被测代码在底部进入else块(这样做)。最后,userRepository
模拟为Optional.absent()
返回tokenUseCases
,应该使运行时进入Completable.complete()
块。但是,在运行时,将永远不会到达deleteTokenOperation
块,并且整个链不会出现错误。我不知道为什么会这样,有人有什么想法吗?
编辑:
有人问我为什么要使用andThen
,这是因为andThen
类型的Publisher<Single<T>>
方法需要它:
retryWhen
Single
@CheckReturnValue
@SchedulerSupport(SchedulerSupport.NONE)
public final Single<T> retryWhen(Function<? super Flowable<Throwable>, ? extends Publisher<?>> handler) {
return toSingle(toFlowable().retryWhen(handler));
}
没有:
Observable
编辑2:
此处调用私有函数wrapApiRequestSingle的受测试代码(以澄清问题):
retryWhen
编辑3:
采用TDD方法编写函数,方法是在每次添加新行时完全重新开始并运行测试。现在该函数如下所示:
@CheckReturnValue
@SchedulerSupport(SchedulerSupport.NONE)
public final Observable<T> retryWhen(
final Function<? super Observable<Throwable>, ? extends ObservableSource<?>> handler) {
ObjectHelper.requireNonNull(handler, "handler is null");
return RxJavaPlugins.onAssembly(new ObservableRetryWhen<T>(this, handler));
}
但是,编译器存在一个问题,即弄清楚如何解决接下来要调用的onErrorResume的重载。我尝试通过在lambda参数上提供一种类型来明确声明重载,但编译器仍在抱怨模棱两可的类型eval。
答案 0 :(得分:0)
根据我的评论,我的意思是
private fun <T> wrapApiRequestSingle(apiCall: () -> Single<T>, token: Token) : Single<T> =
Single.defer {
apiCall.invoke()
}.retryWhen { obsError ->
obsError.flatMap<T> { error -> // <---------------------------------------
when (error) {
is TokenExpiredException -> {
userRepository.getLoggedInUser()
.toFlowable()
.flatMap { userOptional ->
Publisher<Single<T>> {
if (userOptional.isPresent) {
mobileRemote.swapRefreshTokenForAccessToken(
token.refreshToken, userOptional.get().emailAddress)
.onErrorResumeNext { refreshError ->
EventReporter.e(TAG, "Failed to refresh JWT.", refreshError)
tokenUseCases.deleteToken()
.andThen(preferences
.singleOrError()
.flatMap { prefs ->
prefs.apply {
this.pushRegistrationId = ""
this.token = null
}.apply()
Single.error<Token>(NoLoggedInUserException())
})
}
} else {
EventReporter.e(TAG, "No user was logged in.", error)
tokenUseCases.deleteToken()
.andThen(preferences
.singleOrError()
.flatMap { prefs ->
prefs.apply {
this.pushRegistrationId = ""
this.token = null
}.apply()
Single.error<Token>(NoLoggedInUserException())
})
}
}
}.flatMapSingle { it } // <------------------------------------
}
else -> {
Flowable.error(error)
}
}
}
}