Debounce ignoring timeout in unit tests - java

I have this method to fetch search result from api
public void fetchSearchResults(Observable<String> searchObservable) {
searchObservable
.filter(search -> !TextUtils.isEmpty(search))
.debounce(700, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(search -> getView().showLoader)
.switchMap(search -> apiService.fetchSearchResults(search)) //Api call is executed on an io scheduler
.subscribe(consumer, errorConsumer);
}
and I wrote this JUnit test for this method:
#Test
public void fetchSearchResultsTest() {
TestScheduler testScheduler = new TestScheduler();
Observable<String> nameObservable = Observable.just("","FA")
.concatMap(search -> Observable.just(search).delay(100,
TimeUnit.MILLISECONDS, testScheduler));
testScheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS);
verify(view, never()).showLoader();
testScheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS);
verify(view, never()).showLoader();
}
But the test fails on the last verify statement with message
org.mockito.exceptions.verification.NeverWantedButInvoked
view.showLoader();
I have tried passing a TestScheduler to the debounce operator and setting the default computation scheduler as a TestScheduler through RxJavaPlugins but the result does not change, the test still fails.
If the test is failing then that would mean that the debounce operator is sending the event right through it ignoring timeout passed in it's arguments. I don't know if this correct but this is as far I understand. So, my question is how can I fix this test and control the events from the debounce operator like I'm doing with the source observable with TestSchedulers?

Your test is failing because of the onCompleted() that occurs when the second item is emitted. The documentation says that debounce() will issue the final item immediately that it receives onCompleted().
To make your test work, either concatenate an Observable.never(), or add more items into the pipeline.
Here is an article on using debounce for doing auto-completion.

Related

Testing internal Flux.interval

I want to provide some data using Reactor's Flux. Since it may take a lot of time to provide this data, I decided to introduce a ping mechanism (e.g. to keep tcp connection alive and not get timeouts). Here is my simplified solution:
public class Example {
private final DataProvider dataProvider;
public Example(DataProvider dataProvider) {
this.dataProvider = dataProvider;
}
public Flux<String> getData() {
AtomicBoolean inProgress = new AtomicBoolean(true);
Flux<String> dataFlux = dataProvider.provide()
.doFinally(ignoreIt -> inProgress.set(false));
return dataFlux.mergeWith(ping(inProgress::get));
}
private Publisher<String> ping(Supplier<Boolean> inProgress) {
return Flux.interval(Duration.ofSeconds(1), Duration.ofSeconds(1))
.map((tick) -> "ping " + tick)
.takeWhile(ignoreIt -> inProgress.get());
}
interface DataProvider {
Flux<String> provide();
}
public static void main(String[] args) {
Callable<String> dataProviderLogic = () -> {
Thread.sleep(3500);
return "REAL DATA - SHOULD TERMINATE PING";
};
// wrapping synchronous call
DataProvider dataProvider = () -> Mono.fromCallable(dataProviderLogic)
.subscribeOn(Schedulers.boundedElastic())
.flux();
new Example(dataProvider).getData()
.doOnNext(data -> System.out.println("GOT: " + data))
.blockLast();
}
}
Above code prints on console:
GOT: ping 0
GOT: ping 1
GOT: ping 2
GOT: REAL DATA - SHOULD TERMINATE PING
So it works as expected.
The question is: how can I test this ping mechanism in a Junit5 test, so it won't take a lot of time (e.g. several seconds)?
In an ideal world I would like to write a test which imitates a delay for the data provision, check if expected number of pings was generated and verify if complete signal was emitted (to make sure that ping flux terminates as expected). Of course I would like to have a unit test, which can be run in ms.
I tried this, but with no luck:
#Test
void test() {
TestPublisher<String> publisher = TestPublisher.create();
Flux<String> data = new Example(publisher::flux).getData();
StepVerifier.withVirtualTime(() -> data)
.thenAwait(Duration.ofMillis(3500))
.then(() -> publisher.emit("REAL DATA - SHOULD TERMINATE PING"))
.then(publisher::complete)
.expectNextCount(4)
.verifyComplete();
}
Above test ends up with this error:
java.lang.AssertionError: expectation "expectNextCount(4)" failed (expected: count = 4; actual: counted = 1; signal: onComplete())
Is it possible at all to use virtual time for internally created Flux.interval?
Any ideas for an alternative ping solution will be appreciated.
Despite of the fact that above ping mechanism is not the best one (I suggest to use Sink instead of AtomicBoolean and use takeUntilOther instead of takeWhile), in my case the problem was probably related to the situation where not all flux instructions were wrapped with withVirtualTime. This code works as expected in the above case:
#Test
void test() {
StepVerifier.withVirtualTime(() -> {
Flux<String> data = Flux.just("REAL DATA - SHOULD TERMINATE PING").delayElements(Duration.ofMillis(3200));
return new Example(() -> data).getData();
})
.thenAwait(Duration.ofMillis(3500))
.expectNextCount(4)
.thenAwait(Duration.ofMillis(1000))
.verifyComplete();
}

Asynchronous Java: Help flatten my nested Mono

Setup:
public Mono<Mono<String>> getAsyncResult() { // should return Mono<String>
return Mono.fromSupplier(() -> {
if (stopEarly()) return Mono.just("STOPPED EARLY");
int a = doSyncJob1();
int b = doSyncJob2();
return doAsyncJob(a, b).map(string1 -> toString2(string1));
});
}
Right now the whole thing returns Mono<Mono<String>>. How to get it to return Mono<String> without blocking?
The reason it's all inside Mono.fromSupplier() is because I don't need the tasks to necessarily block and happen immediately, they can be scheduled to run asynchronously. Maybe one way is to flatten what's inside Mono.fromSupplier() but I'm not sure how to compose it.
Replace Mono.fromSupplier with Mono.defer
Also, if doSyncJob* blocks, then they will block the subscriber thread. Therefore, you might want to use .subscribeOn(Schedulers.elastic()) after .defer(...) to ensure the blocking work is executed in a Scheduler meant for blocking work.

Single that emits a value that passed function returned

I want a Single that calls a certain function and then completes with the value that function has returned.
The following is something similar:
Single.fromCallable(this::func);
The problem with that is that it calls this::func every time a subscriber is added. So if this::func counts calls that single would return 3 to a third subscriber it gets.
I see that as a problem, because, what if this::func were a long running operation.
And I don't get it, does that mean that Single::onComplete has been called twice? Which I thought it was impossible, and it doesn't make sense, because, how can something complete twice?
And since I'm an Android programmer Single::fromFuture doesn't work here, is there some alternative to it?
I will demonstrate my problem with the following example:
class SingleFromCallableTest {
int funcCalls = 0;
int func(){
return funcCalls++;
}
#Test
public void run(){
Single<Integer> source = Single.fromCallable(this::func);
source.subscribe(System.out::println); // prints 1
source.subscribe(System.out::println); // prints 2
}
}
IMO, second subscriber shouldn't have been called because Single should succed just once IMO.
Just like with SingleSubject, once you call onSuccess, it cannot succed again.
If Single.fromCallable would work the way it think it should, than, in the previouse example, source could have completed even before the first subscriber subscribed, which means that, only the following way of subscribing would make sense:
Single.fromCallable(this::func).subscribe(System.out.println);
But actually, maybe even then it's possible not to catch a value emited by single, maybe that's way this is not possible.
The method #fromCallable is a factory and will return a new Single every time. On every subscription you will subscribe to a new Single. Therefore the function will be invoked for every subscriber. If you want to cache the value, you would use #cache operator. Please have a look at provided two tests.
The test 'notCached' will invoke the function for each subscription. The test 'cached' will invoke the function only one time. If you want to share the result, just re-use create Single#fromCallable with #cache operator.
Environment
dependencies {
compile 'io.reactivex.rxjava2:rxjava:2.1.6'
compile 'org.mockito:mockito-core:2.11.0'
testCompile("org.junit.jupiter:junit-jupiter-api:5.0.0")
testRuntime("org.junit.jupiter:junit-jupiter-engine:5.0.0")
Tests
#Test
void notCached() throws Exception {
Callable<Integer> mock = mock(Callable.class);
when(mock.call()).thenReturn(10);
Single<Integer> integerSingle = Single.fromCallable(mock);
Disposable subscribe1 = integerSingle.subscribe();
Disposable subscribe2 = integerSingle.subscribe();
verify(mock, times(2)).call();
}
#Test
void cached() throws Exception {
Callable<Integer> mock = mock(Callable.class);
when(mock.call()).thenReturn(10);
Single<Integer> integerSingle = Single.fromCallable(mock).cache();
Disposable subscribe1 = integerSingle.subscribe();
Disposable subscribe2 = integerSingle.subscribe();
Disposable subscribe3 = integerSingle.subscribe();
Disposable subscribe4 = integerSingle.subscribe();
verify(mock, times(1)).call();
}

Proper termination of a stuck Couchbase Observable

I'm trying to delete a batch of couchbase documents in rapid fashion according to some constraint (or update the document if the constraint isn't satisfied). Each deletion is dubbed a "parcel" according to my terminology.
When executing, I run into a very strange behavior - the thread in charge of this task starts working as expected for a few iterations (at best). After this "grace period", couchbase gets "stuck" and the Observable doesn't call any of its Subscriber's methods (onNext, onComplete, onError) within the defined period of 30 seconds.
When the latch timeout occurs (see implementation below), the method returns but the Observable keeps executing (I noticed that when it kept printing debug messages when stopped with a breakpoint outside the scope of this method).
I suspect couchbase is stuck because after a few seconds, many Observables are left in some kind of a "ghost" state - alive and reporting to their Subscriber, which in turn have nothing to do because the method in which they were created has already finished, eventually leading to java.lang.OutOfMemoryError: GC overhead limit exceeded.
I don't know if what I claim here makes sense, but I can't think of another reason for this behavior.
How should I properly terminate an Observable upon timeout? Should I? Any other way around?
public List<InfoParcel> upsertParcels(final Collection<InfoParcel> parcels) {
final CountDownLatch latch = new CountDownLatch(parcels.size());
final List<JsonDocument> docRetList = new LinkedList<JsonDocument>();
Observable<JsonDocument> obs = Observable
.from(parcels)
.flatMap(parcel ->
Observable.defer(() ->
{
return bucket.async().get(parcel.key).firstOrDefault(null);
})
.map(doc -> {
// In-memory manipulation of the document
return updateDocs(doc, parcel);
})
.flatMap(doc -> {
boolean shouldDelete = ... // Decide by inner logic
if (shouldDelete) {
if (doc.cas() == 0) {
return Observable.just(doc);
}
return bucket.async().remove(doc);
}
return (doc.cas() == 0 ? bucket.async().insert(doc) : bucket.async().replace(doc));
})
);
obs.subscribe(new Subscriber<JsonDocument>() {
#Override
public void onNext(JsonDocument doc) {
docRetList.add(doc);
latch.countDown();
}
#Override
public void onCompleted() {
// Due to a bug in RxJava, onError() / retryWhen() does not intercept exceptions thrown from within the map/flatMap methods.
// Therefore, we need to recalculate the "conflicted" parcels and send them for update again.
while(latch.getCount() > 0) {
latch.countDown();
}
}
#Override
public void onError(Throwable e) {
// Same reason as above
while (latch.getCount() > 0) {
latch.countDown();
}
}
};
);
latch.await(30, TimeUnit.SECONDS);
// Recalculating remaining failed parcels and returning them for another cycle of this method (there's a loop outside)
}
I think this is indeed due to the fact that using a countdown latch doesn't signal the source that the flow of data processing should stop.
You could use more of rxjava, by using toList().timeout(30, TimeUnit.SECONDS).toBlocking().single() instead of collecting in an (un synchronized and thus unsafe) external list and of using the countdownLatch.
This will block until a List of your documents is returned.
When you create your couchbase env in code, set computationPoolSize to something large. When the Couchbase clients runs out of threads using async it just stops working, and wont ever call the callback.

RxJava: Subscription's unsubscribe() method isn't invoked

In code bellow I need to release some resources on unsubscription (where it logs "release").
Observable first = Observable.create(new Observable.OnSubscribe<Object>() {
#Override
public void call(Subscriber<? super Object> subscriber) {
subscriber.add(Subscriptions.create(() -> {
log(“release”);
}));
}
}).doOnUnsubscribe(() -> log(“first”));
Observable second = Observable.create(…).doOnUnsubscribe(() -> log(“second”));
Observable result = first.mergeWith(second).doOnUnsubscribe(() -> log(“result”));
Subscription subscription = result.subscribe(…);
//…
subscription.unsubscribe();
But it logs only “result”. Looks like unsubscription is not propagated to merge’s child observables. So how to handle unsubscription inside of first observable’s Observable.OnSubscribe?
Most of the time, calling unsubscribe has only effect on a live sequence and may not propagate if certain sequences have completed: the operators may not keep their sources around so they can avoid memory leaks. The main idea would be that operators release any resources they manage on termination just before or just after they call their downstream's onError or onCompleted methods, but this is somewhat inconsistent with 1.x.
If you want to make sure resources are releases, look at the using operator which will release your resource upon termination or unsubscription:
Observable.using(
() -> "resource",
r -> Observable.just(r),
r -> System.out.println("Releasing " + r))
.subscribe(System.out::println);

Categories