Android单元测试:如何测试来自RxJava的retryWhen是否被称为某些数字? 我所拥有的
我有一个静态类Android单元测试:如何测试来自RxJava的retryWhen是否被称为某些数字? 我所拥有的,android,unit-testing,junit,mockito,rx-java,Android,Unit Testing,Junit,Mockito,Rx Java,我有一个静态类APIHelper,并且有一个公共函数 /** * Calls APIs that return Observable<Response<*>>. * @param observable an Observable<Response<SomeModel>> you can get from NetworkServiceGenerator.getApi() * @param maxRetryCount retry count to
APIHelper
,并且有一个公共函数
/**
* Calls APIs that return Observable<Response<*>>.
* @param observable an Observable<Response<SomeModel>> you can get from NetworkServiceGenerator.getApi()
* @param maxRetryCount retry count to try up to. Default is 3 (see DEFAULT_RETRY_COUNT)
* @param customErrorToCatch if included, you can throw any Exception with custom logic
* @param haltIfThrownType if included, you can halt the operation even before try count didn't reach retry count
*/
fun <T : Response<*>> call(
observable: Observable<T>,
maxRetryCount: Int = DEFAULT_MAX_RETRY_COUNT,
retryIntervalInSec: Long = DEFAULT_RETRY_INTERVAL_SEC,
customErrorToCatch: ((response: T) -> Unit)? = null,
haltIfThrownType: ((throwable: Throwable?) -> Boolean)? = null): Observable<T> {
return observable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext {
if (!it.isSuccessful) {
if (customErrorToCatch != null) {
customErrorToCatch(it)
}
throwNetworkException(it)
}
}
.retryWhen { errors ->
errors.zipWith(
Observable.range(1, maxRetryCount),
BiFunction { throwable: Throwable, count: Int -> Pair(throwable, count) })
.flatMap { pair: Pair<Throwable, Int> ->
val throwable = pair.first
val currentCount = pair.second
val isNetworkException = pair.first is NetworkException
if (shouldStopRetrying(haltIfThrownType, throwable, currentCount, maxRetryCount)) {
Observable.error(throwable)
} else {
Observable.timer(retryIntervalInSec, TimeUnit.SECONDS)
}
}
}
}
我的测试是
@Test
fun testRetriableError() {
val response = Response.error<ResponseModel>(
500,
ResponseBody.create(
MediaType.parse("application/json"),
"{\"result\":\"fail\",\"errorCode\":\"4\",\"errorMessage\":\"\"}")
)
Mockito.`when`(api.requestLogout()).thenReturn(Observable.just(response))
val testScheduler = TestScheduler(0, TimeUnit.SECONDS)
val result = api.requestLogout().call(
observeOn = testScheduler
)
val testObserver = TestObserver<Response<ResponseModel>>()
result.subscribe(testObserver)
testScheduler.advanceTimeBy(1, TimeUnit.SECONDS)
testObserver.assertNotTerminated()
testScheduler.advanceTimeBy(1, TimeUnit.SECONDS)
testObserver.assertNotTerminated()
testScheduler.advanceTimeBy(1, TimeUnit.SECONDS)
testObserver.assertNotTerminated()
testScheduler.advanceTimeBy(1, TimeUnit.SECONDS)
testObserver.assertTerminated() // <--- fails
testObserver.assertNotComplete()
testObserver.assertError(NetworkException(500, "Internal Server Error"))
testObserver.assertValueCount(0)
}
但是,当我调试这个测试并逐行运行它们(花费足够的时间)时,测试通过了
我几乎觉得testScheduler.advanceTimeBy(1,TimeUnit.SECONDS)
什么都不做
我做错了什么?1。使用扩展函数,而不是传入可观察对象。2.使用默认参数传入subscribeOn/observeOn和timer的计划程序。然后,您可以传入TestScheduler并提前时间,以便测试在X次重试后发生的情况。如果您仍然有问题,请随时联系。我们将立即尝试并回复您。感谢您帮助@HansWurst@我编辑这个问题是为了在你的评论之后添加我尝试过的内容。我感觉我就快到了。你必须把一个调度程序传递到
Observable.timer(retryIntervalInSec,TimeUnit.SECONDS)
。否则,它将使用默认的调度程序。这里有一个重载,您可以传入一个调度程序,比如#timer(时间、单位、调度程序)
。您可以只对时间作业使用Schedulers.single()
,但请记住将其作为默认参数传入,以便在Testscheduler上提升时间,否则您会出现并发问题,以便将测试junit线程与传入的scheduler.1同步。使用扩展函数,而不是传入可观察对象。2.使用默认参数传入subscribeOn/observeOn和timer的计划程序。然后,您可以传入TestScheduler并提前时间,以便测试在X次重试后发生的情况。如果您仍然有问题,请随时联系。我们将立即尝试并回复您。感谢您帮助@HansWurst@我编辑这个问题是为了在你的评论之后添加我尝试过的内容。我感觉我就快到了。你必须把一个调度程序传递到Observable.timer(retryIntervalInSec,TimeUnit.SECONDS)
。否则,它将使用默认的调度程序。这里有一个重载,您可以传入一个调度程序,比如#timer(时间、单位、调度程序)
。您可以只对时间作业使用Schedulers.single()
,但请记住将其作为默认参数传入,以便在Testscheduler上提升时间,否则您会遇到并发问题,以便将测试junit线程与传入的调度程序同步。
fun <T: Response<*>> Observable<T>.call(
subscribeOn: Scheduler = Schedulers.io(),
observeOn: Scheduler = AndroidSchedulers.mainThread(),
maxRetryCount: Int = DEFAULT_MAX_RETRY_COUNT,
retryIntervalInSec: Long = DEFAULT_RETRY_INTERVAL_SEC,
customErrorToCatch: ((response: T) -> Unit)? = null,
haltIfThrownType: ((throwable: Throwable?) -> Boolean)? = null): Observable<T> {
return this.subscribeOn(subscribeOn)
.observeOn(observeOn)
.doOnNext {
if (!it.isSuccessful) {
if (customErrorToCatch != null) {
customErrorToCatch(it)
}
throwNetworkException(it)
}
}
.retryWhen { errors ->
errors.zipWith(
Observable.range(1, maxRetryCount),
BiFunction { throwable: Throwable, count: Int -> Pair(throwable, count) })
.flatMap { pair: Pair<Throwable, Int> ->
val throwable = pair.first
val currentCount = pair.second
if (shouldStopRetrying(haltIfThrownType, throwable, currentCount, maxRetryCount)) {
Observable.error(throwable)
} else {
Observable.timer(retryIntervalInSec, TimeUnit.SECONDS)
}
}
}
}
@Test
fun testRetriableError() {
val response = Response.error<ResponseModel>(
500,
ResponseBody.create(
MediaType.parse("application/json"),
"{\"result\":\"fail\",\"errorCode\":\"4\",\"errorMessage\":\"\"}")
)
Mockito.`when`(api.requestLogout()).thenReturn(Observable.just(response))
val testScheduler = TestScheduler(0, TimeUnit.SECONDS)
val result = api.requestLogout().call(
observeOn = testScheduler
)
val testObserver = TestObserver<Response<ResponseModel>>()
result.subscribe(testObserver)
testScheduler.advanceTimeBy(1, TimeUnit.SECONDS)
testObserver.assertNotTerminated()
testScheduler.advanceTimeBy(1, TimeUnit.SECONDS)
testObserver.assertNotTerminated()
testScheduler.advanceTimeBy(1, TimeUnit.SECONDS)
testObserver.assertNotTerminated()
testScheduler.advanceTimeBy(1, TimeUnit.SECONDS)
testObserver.assertTerminated() // <--- fails
testObserver.assertNotComplete()
testObserver.assertError(NetworkException(500, "Internal Server Error"))
testObserver.assertValueCount(0)
}
java.lang.AssertionError: Subscriber still running! (latch = 1, values = 0, errors = 0, completions = 0)