I have This method that calls a Rest API and returns the result as an Observable (Single):
fun resetPassword(email: String): Single<ResetPassword> {
return Single.create { emitter ->
val subscription = mApiInterfacePanda.resetPassword(email)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe({ resetPasswordResponse ->
when(resetPasswordResponse.code()) {
200 -> {
resetPasswordResponse?.body()?.let { resetPassword ->
emitter.onSuccess(resetPassword)
}
}
else -> emitter.onError(Exception("Server Error"))
}
}, { throwable ->
emitter.onError(throwable)
})
mCompositeDisposable.add(subscription)
}
}
Unit Test:
#Test
fun resetPassword_200() {
val response = Response.success(200, sMockResetPasswordResponse)
Mockito.`when`(mApiInterfacePanda.resetPassword(Mockito.anyString()))
.thenReturn(Single.just(response))
mTokenRepository.resetPassword(MOCK_EMAIL)
val observer = mApiInterfacePanda.resetPassword(MOCK_EMAIL)
val testObserver = TestObserver.create<Response<ResetPassword>>()
observer.subscribe(testObserver)
testObserver.assertSubscribed()
testObserver.awaitCount(1)
testObserver.assertComplete()
testObserver.assertResult(response)
}
My Problem is only this line gets covered and the other lines won't run and that has a lot of impact on my total test coverage:
return Single.create { emitter ->
There's more than one thing going on here if I'm not mistaken. Let's take it in parts.
First, your "internal" observer:
mApiInterfacePanda.resetPassword(email)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe({ resetPasswordResponse -> ... })
Is observing on the android main thread and executing on a background thread. To the best of my knowledge, in most cases, the test thread will end before your mApiInterfacePanda .resetPassword has a chance to finish and run. You didn't really post the test setup, so I'm not sure if this is an actual issue, but in any case it's worth mentioning. Here's 2 ways to fix this:
RxJavaPlugins and RxAndroidPlugins
RxJava already provides a way to change the schedulers that are provided. An example is RxAndroidPlugins.setMainThreadSchedulerHandler. Here's how it could help:
#Before
fun setUp() {
RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }
RxJavaPlugins.setInitIoSchedulerHandler { Schedulers.trampoline() }
}
The above methods make sure that everywhere you use the main thread scheduler and the io scheduler, it'll instead return the trampoline scheduler. This is a scheduler that guarantees that the code is executed in the same thread that was executing previously. In other words, it'll make sure you run it on the unit test main thread.
You will have to undo these:
#After
fun tearDown() {
RxAndroidPlugins.reset()
RxJavaPlugins.reset()
}
You can also change other schedulers.
Inject the schedulers
You can use kotlin's default arguments to help out with injecting schedulers:
fun resetPassword(
email: String,
obsScheduler: Scheduler = AndroidSchedulers.mainThread(),
subScheduler: Scheduler = Schedulers.io()
): Single<ResetPassword> {
return Single.create { emitter ->
val subscription = mApiInterfacePanda.resetPassword(email)
.observeOn(obsScheduler)
.subscribeOn(subScheduler)
.subscribe({ resetPasswordResponse ->
when(resetPasswordResponse.code()) {
200 -> {
resetPasswordResponse?.body()?.let { resetPassword ->
emitter.onSuccess(resetPassword)
}
}
else -> emitter.onError(Exception("Server Error"))
}
}, { throwable ->
emitter.onError(throwable)
})
mCompositeDisposable.add(subscription)
}
}
At test time you can just call it like resetPassword("foo#bar.com", Schedulers.trampoline(), Schedulers.trampoline() and for the application just pass in the email.
The other thing I see here is maybe not related to the problem, but I think it's still good to know. First, you're creating a single, but you don't need to do this.
Single.create is usually used when you don't have reactive code. However, mApiInterfacePanda.resetPassword(email) already returns a reactive component and although I'm not sure, let's just assume it's a single. If not, it should be fairly simple to convert it to something else.
You're also holding on to a disposable, which from what I can tell shouldn't be necessary.
Lastly, you're using retrofit according to your tags so you don't need to make the call return a raw response unless extremely necessary. This is true because retrofit checks the status code for you and will deliver the errors inside onError with an http exception. This is the Rx way of handling the errors.
With all this in mind, I'd rewrite the entire method like this:
fun resetPassword(email: String) = mApiInterfacePanda.resetPassword(email)
(note that resetPassword must not return a raw response, but Single<ResetPassword>
It actually shouldn't need anything else. Retrofit will make sure things end up in either onSuccess or onError. You don't need to subscribe to the result of the api here and handle disposables - let whoever is calling this code handle it.
You may also notice that if this is the case, then the solution for the schedulers is not needed. I guess this is true in this case, just remember some operators operate in some default schedulers and you may need to override them in some cases.
So how would I test the above method?
Personally I'd just check if the method calls the api with the right parameters:
#Test
fun resetPassword() {
mTokenRepository.resetPassword(MOCK_EMAIL)
verify(mApiInterfacePanda).resetPassword(MOCK_EMAIL)
}
I don't think there's much more needed here. There's no more logic I can see in the rewritten method.
Related
I'm creating offline first app as my side project using rxKotlin, MVVM + Clean Architecture and yesterday I decided to get ride off boilerplate subscribeOn and observeOn by using transformers. I quickly realized that apply function of transformers are ignored.
Here is code of my base completable use case (interactor):
abstract class CompletableUseCase(private val transformer: CompletableTransformer) {
abstract fun createCompletable(data: Map<String, Any>? = null) : Completable
fun completable(data: Map<String, Any>? = null) : Completable {
return createCompletable(data).compose(transformer)
}
}
And here is implementation of specific interactor:
class SaveRouteInteractor(
transformer: CompletableTransformer,
private val routeRepository: RouteRepository
) : CompletableUseCase(transformer) {
companion object {
private const val PARAM_ROUTE = "param_route"
}
fun saveRoute(route: Route) : Completable {
val data = HashMap<String, Route>()
data[PARAM_ROUTE] = route
return completable(data)
}
override fun createCompletable(data: Map<String, Any>?): Completable {
val routeEntity = data?.get(PARAM_ROUTE)
routeEntity?.let {
return routeRepository.saveRoute(routeEntity as Route)
} ?: return Completable.error(IllegalArgumentException("Argument #route must be provided."))
}
}
My custom transformer that is passed to the constructor of SaveRouteInteractor:
class IOCompletableTransformer(private val mainThreadScheduler: Scheduler) : CompletableTransformer {
override fun apply(upstream: Completable): CompletableSource {
return upstream.subscribeOn(Schedulers.io()).observeOn(mainThreadScheduler)
}
}
And implementation of RouteRepository method:
override fun saveRoute(route: Route): Completable {
return localRouteSource.saveRoute(route)
.flatMap { localID ->
route.routeId = localID
remoteRouteSource.saveRoute(route)
}
.flatMapCompletable { localRouteSource.updateRouteID(route.routeId, it) }
}
I'm using Room as my local source so after calling save interactor in my ViewModel I'm getting IlligalStateException telling me that I'm not allowed to access database on the main thread.
Maybe I'm missing something but it seems that transform function is ignored. I debugged this method and it is applying subscribeOn and observeOn to the upstream.
Thanks for help in advance,
Pace!
It's hard to tell you where the issue is because the code is partial.
For example here:
return localRouteSource.saveRoute(route)
.flatMap { localID ->
route.routeId = localID
remoteRouteSource.saveRoute(route)
}
.flatMapCompletable { localRouteSource.updateRouteID(route.routeId, it) }
I suppose the localRouteSource.saveRoute() is using the interactor you show us but it is not clear how remoteRouteSource.saveRoute() or localRouteSource.updateRouteID() are implemented.
they also need to be subscribed on the IO thread.
As a rule of thumb you should switch thread when you KNOW that you need it.
In other words, you should use subscribeOn() in places where you know you are doing IO as close as possible to the actual job. ObserveOn instead is to be used when you know you need to obtain those results in the UI thread and that you might get in some other thread.
in your example there's absolutely no need to keep using observeOn(MAIN_THREAD), the only time you do need it (I suppose) is when you want to show the result.
A couple of other things:
This code
override fun createCompletable(data: Map<String, Any>?): Completable {
val routeEntity = data?.get(PARAM_ROUTE)
routeEntity?.let {
return routeRepository.saveRoute(routeEntity as Route)
} ?: return Completable.error(IllegalArgumentException("Argument #route must be provided."))
}
it is evaluated at the time when the method is called rather then when the completable is subscribed.
In other words it break the Rx contract and compute data?.get(PARAM_ROUTE) when you call the method. If it is immutable there's no much difference, but if it can change value during execution it should be wrapped in a Completable.defer { }
Finally, here
.flatMap { localID ->
route.routeId = localID
remoteRouteSource.saveRoute(route)
}
you are modyfing something outside the chain (route.routeId = localID), this is called a side effect.
be careful with those kind of stuff, Rx is build in a way that is safer to be used with immutable objects.
I personally wouldn't mind too much as long as you understand what's going on and when it could create issues.
I currently have an EditText for the user to enter a search. I'm trying to use RxJava with debounce to only search every so often, instead of each character. However, I'm getting an InterruptedIOException while I'm testing, which kills the stream.
private val subject = BehaviorSubject.create<String>()
init {
configureAutoComplete()
}
private fun configureAutoComplete() {
subject.debounce(200, TimeUnit.MILLISECONDS)
.flatMap {
getSearchResults(query = it)
}
.subscribe({ result ->
handleResult(result)
}, { t: Throwable? ->
Logger.e(t, "Failed to search")
})
}
fun getSearchResults(query: String): Observable<List<MyObject>> {
val service = NetworkService.create() // get retrofit service
return service.search(query)
}
fun search(text: String) {
subject.onNext(text)
}
As you can see, I'm creating a BehaviorSubject, and within init I'm setting it up with debounce.
getSearchResult returns an Observable and does my network request.
But as I'm testing, if I type at a specific rate ( usually quick-ish, like typing another character while the request is ongoing ) it'll throw an Exception.
Failed to search : java.io.InterruptedIOException
at okhttp3.internal.http2.Http2Stream.waitForIo(Http2Stream.java:579)
at okhttp3.internal.http2.Http2Stream.takeResponseHeaders(Http2Stream.java:143)
at okhttp3.internal.http2.Http2Codec.readResponseHeaders(Http2Codec.java:125)
I was looking at this, https://stackoverflow.com/a/47276430/3106174, and it seems like I'm doing everything correctly.
After more testing, I realized that the network request was on the main thread.
You can test this by replacing your network call with Observerable.create{ ... } and throwing a Thread.sleep(1000) inside.
I was following this tutorial, https://proandroiddev.com/building-an-autocompleting-edittext-using-rxjava-f69c5c3f5a40, and one of the comments mention this issue.
"But I think one thing is misleading in your code snippet, and it’s
that subjects aren’t thread safe. And the thread that your code will
run on will be the thread that you emitting on (in this case the main
thread). "
To solve this issue, you need to force it to run on Schedulers.io(). Make sure it's after the debounce or it won't work.
private fun configureAutoComplete() {
subject.debounce(200, TimeUnit.MILLISECONDS)
.observeOn(Schedulers.io()) // add this here
.distinctUntilChanged()
.switchMap {
getSearchResults(query = it)
}
.subscribe({ result ->
handleResult(result)
}, { t: Throwable? ->
Logger.e(t, "Failed to search")
})
}
I'm trying to use Kotlin's coroutines to avoid callback hell, but it doesnt look like I can in this specific situation, I would like some thougths about it.
I have this SyncService class which calls series of different methods to send data to the server like the following:
SyncService calls Sync Student, which calls Student Repository, which calls DataSource that makes a server request sending the data through Apollo's Graphql Client.
The same pattern follows in each of my features:
SyncService -> Sync Feature -> Feature Repository -> DataSource
So every one of the method that I call has this signature:
fun save(onSuccess: ()-> Unit, onError:()->Unit) {
//To Stuff here
}
The problem is:
When I sync and successfully save the Student on server, I need to sync his enrollment, and if I successfully save the enrollment, I need to sync another object and so on.
It all depends on each other and I need to do it sequentially, that's why I was using callbacks.
But as you can imagine, the code result is not very friendly, and me and my team starting searching for alternatives to keep it better. And we ended up with this extension function:
suspend fun <T> ApolloCall<T>.execute() = suspendCoroutine<Response<T>> { cont ->
enqueue(object: ApolloCall.Callback<T>() {
override fun onResponse(response: Response<T>) {
cont.resume(response)
}
override fun onFailure(e: ApolloException) {
cont.resumeWithException(e)
}
})
}
But the function in DataSource still has a onSuccess() and onError() as callbacks that needs to be passed to whoever call it.
fun saveStudents(
students: List<StudentInput>,
onSuccess: () -> Unit,
onError: (errorMessage: String) -> Unit) {
runBlocking {
try {
val response = GraphQLClient.apolloInstance
.mutate(CreateStudentsMutation
.builder()
.students(students)
.build())
.execute()
if (!response.hasErrors())
onSuccess()
else
onError("Response has errors!")
} catch (e: ApolloException) {
e.printStackTrace()
onError("Server error occurred!")
}
}
}
The SyncService class code changed to be like:
private fun runSync(onComplete: () -> Unit) = async(CommonPool) {
val syncStudentProcess = async(coroutineContext, start = CoroutineStart.LAZY) {
syncStudents()
}
val syncEnrollmentProcess = async(coroutineContext, start = CoroutineStart.LAZY) {
syncEnrollments()
}
syncStudentProcess.await()
syncEnrollmentProcess.await()
onComplete()
}
It does execute it sequentially, but I need a way to stop every other coroutine if any got any errors. Error that might come only from Apollo's
So I've been trying a lot to find a way to simplify this code, but didn't get any good result. I don't even know if this chaining of callbacks can be simplify at all. That's why I came here to see some thoughts on it.
TLDR: I want a way to execute all of my functions sequentially, and still be able to stop all coroutines if any got an exception without a lot o chaining callbacks.
I am testing Kotlin coroutines in my Android app and I am trying to do the following unit test
#Test fun `When getVenues success calls explore venues net controller and forwards result to listener`() =
runBlocking {
val near = "Barcelona"
val result = buildMockVenues()
val producerJob = produce<List<VenueModel>>(coroutineContext) { result.value }
whenever(venuesRepository.getVenues(eq(near))) doReturn producerJob // produce corooutine called inside interactor.getVenues(..)
interactor.getVenues(near, success, error) // call to real method
verify(venuesRepository).getVenues(eq(near))
verify(success).invoke(argThat {
value == result.value
})
}
The interactor method is as follows
fun getVenues(near: String, success: Callback<GetVenuesResult>,
error: Callback<GetVenuesResult>) =
postExecute {
repository.getVenues(near).consumeEach { venues ->
if (venues.isEmpty()) {
error(GetVenuesResult(venues, Throwable("No venues where found")))
} else {
success(GetVenuesResult(venues))
}
}
}
postExecute{..} is a method on a BaseInteractor that executes the function in the ui thread through a custom Executor that uses the launch(UI) coroutine from kotlin android coroutines library
fun <T> postExecute(uiFun: suspend () -> T) =
executor.ui(uiFun)
Then the repository.getVenues(..) function is also a coroutine that returns the ProducerJob using produce(CommonPool) {}
The problem is that it seams that success callback in the interactor function doesn't seem to be executed as per the
verify(success).invoke(argThat {
value == result.value
})
However, I do see while debugging that the execution in the interactor function reaches to the if (venues.isEmpty()) line inside the consumeEach but then from there exits and continues with the test, obviously failing on the verify for the success callback.
I am a bit new on coroutines so any help would be appreciated.
I figured this one out. I saw that the problem was just with this producing coroutine and not with the others tests that are also using coroutines and working just fine. I noticed that I actually missed the send on the mocked ProducingJob in order to have it actually produce a value, in this case the list of mocks. I just added that changing the mock of the producing job to
val producerJob = produce { send(result.value) }
I have a simple view that displays currencies as list. App fetches currencies from web service every 1 second. So I have following method in my ViewModel:
fun onViewAppeared() {
currenciesViewStateUpdates.onNext(CurrenciesViewState(true, null))
Flowable.interval(1, TimeUnit.SECONDS)
.flatMapSingle { _ -> currenciesService.calculateCurrencies(Currency("EUR", 10.0)) }
.doOnError { error -> Log.d("LOG", error.message) }
.observeOn(AndroidSchedulers.mainThread())
.subscribe { currencies ->
currenciesViewStateUpdates.onNext(CurrenciesViewState(false, currencies))
}
}
Now I need to unit test this behavior:
#Test
fun viewAppearedAndCurrenciesRequestSucceeded_currenciesDisplayed() {
val currencies = listOf(Currency("", 0.0))
Mockito.`when`(currenciesServiceMock.calculateCurrencies(anyCurrency())).thenReturn(Single.just(currencies))
viewModel.getViewStateUpdates().subscribe(testObserver)
viewModel.onViewAppeared()
testObserver.assertSubscribed()
testObserver.assertNoErrors()
testObserver.assertValueCount(2)
assertFalse(testObserver.values()[1].loading)
assertNotNull(testObserver.values()[1].currencies)
testObserver.values()[1].currencies?.let { assertTrue(it.isNotEmpty()) }
}
All RxSchedulers set up for immediate. Problem: with test awaits forever when observable finishes subscription, but it will never finish cause it is infinite interval. How can I test it?
You will have problems running your tests in the way that you have fashioned.
Make your schedulers into class parameters, so that they can be injected in the proper fashion, both during testing and production.
Use test schedulers instead of Schedulers.immediate() because you can end up in a deadlock.
Always have your timed operations (interval(), timeout(), etc) operate on explicit schedulers, usually ones that you have injected.