/**
* Copyright 2014 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package rx.internal.operators;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import org.mockito.InOrder;
import rx.Observable;
import rx.Observable.OnSubscribe;
import rx.Observer;
import rx.Subscriber;
import rx.Subscription;
import rx.exceptions.TestException;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.functions.Func0;
import rx.functions.Func1;
import rx.subscriptions.Subscriptions;
public class OnSubscribeUsingTest {
private interface Resource {
public String getTextFromWeb();
public void dispose();
}
private static class DisposeAction implements Action1<Resource> {
@Override
public void call(Resource r) {
r.dispose();
}
}
private final Action1<Subscription> disposeSubscription = new Action1<Subscription>() {
@Override
public void call(Subscription s) {
s.unsubscribe();
}
};
@Test
public void testUsing() {
performTestUsing(false);
}
@Test
public void testUsingEagerly() {
performTestUsing(true);
}
private void performTestUsing(boolean disposeEagerly) {
final Resource resource = mock(Resource.class);
when(resource.getTextFromWeb()).thenReturn("Hello world!");
Func0<Resource> resourceFactory = new Func0<Resource>() {
@Override
public Resource call() {
return resource;
}
};
Func1<Resource, Observable<String>> observableFactory = new Func1<Resource, Observable<String>>() {
@Override
public Observable<String> call(Resource resource) {
return Observable.from(resource.getTextFromWeb().split(" "));
}
};
@SuppressWarnings("unchecked")
Observer<String> observer = mock(Observer.class);
Observable<String> observable = Observable.using(resourceFactory, observableFactory,
new DisposeAction(), disposeEagerly);
observable.subscribe(observer);
InOrder inOrder = inOrder(observer);
inOrder.verify(observer, times(1)).onNext("Hello");
inOrder.verify(observer, times(1)).onNext("world!");
inOrder.verify(observer, times(1)).onCompleted();
inOrder.verifyNoMoreInteractions();
// The resouce should be closed
verify(resource, times(1)).dispose();
}
@Test
public void testUsingWithSubscribingTwice() {
performTestUsingWithSubscribingTwice(false);
}
@Test
public void testUsingWithSubscribingTwiceDisposeEagerly() {
performTestUsingWithSubscribingTwice(true);
}
private void performTestUsingWithSubscribingTwice(boolean disposeEagerly) {
// When subscribe is called, a new resource should be created.
Func0<Resource> resourceFactory = new Func0<Resource>() {
@Override
public Resource call() {
return new Resource() {
boolean first = true;
@Override
public String getTextFromWeb() {
if (first) {
first = false;
return "Hello world!";
}
return "Nothing";
}
@Override
public void dispose() {
// do nothing
}
};
}
};
Func1<Resource, Observable<String>> observableFactory = new Func1<Resource, Observable<String>>() {
@Override
public Observable<String> call(Resource resource) {
return Observable.from(resource.getTextFromWeb().split(" "));
}
};
@SuppressWarnings("unchecked")
Observer<String> observer = mock(Observer.class);
Observable<String> observable = Observable.using(resourceFactory, observableFactory,
new DisposeAction(), disposeEagerly);
observable.subscribe(observer);
observable.subscribe(observer);
InOrder inOrder = inOrder(observer);
inOrder.verify(observer, times(1)).onNext("Hello");
inOrder.verify(observer, times(1)).onNext("world!");
inOrder.verify(observer, times(1)).onCompleted();
inOrder.verify(observer, times(1)).onNext("Hello");
inOrder.verify(observer, times(1)).onNext("world!");
inOrder.verify(observer, times(1)).onCompleted();
inOrder.verifyNoMoreInteractions();
}
@Test(expected = TestException.class)
public void testUsingWithResourceFactoryError() {
performTestUsingWithResourceFactoryError(false);
}
@Test(expected = TestException.class)
public void testUsingWithResourceFactoryErrorDisposeEagerly() {
performTestUsingWithResourceFactoryError(true);
}
private void performTestUsingWithResourceFactoryError(boolean disposeEagerly) {
Func0<Subscription> resourceFactory = new Func0<Subscription>() {
@Override
public Subscription call() {
throw new TestException();
}
};
Func1<Subscription, Observable<Integer>> observableFactory = new Func1<Subscription, Observable<Integer>>() {
@Override
public Observable<Integer> call(Subscription subscription) {
return Observable.empty();
}
};
Observable.using(resourceFactory, observableFactory, disposeSubscription).toBlocking()
.last();
}
@Test
public void testUsingWithObservableFactoryError() {
performTestUsingWithObservableFactoryError(false);
}
@Test
public void testUsingWithObservableFactoryErrorDisposeEagerly() {
performTestUsingWithObservableFactoryError(true);
}
private void performTestUsingWithObservableFactoryError(boolean disposeEagerly) {
final Action0 unsubscribe = mock(Action0.class);
Func0<Subscription> resourceFactory = new Func0<Subscription>() {
@Override
public Subscription call() {
return Subscriptions.create(unsubscribe);
}
};
Func1<Subscription, Observable<Integer>> observableFactory = new Func1<Subscription, Observable<Integer>>() {
@Override
public Observable<Integer> call(Subscription subscription) {
throw new TestException();
}
};
try {
Observable.using(resourceFactory, observableFactory, disposeSubscription).toBlocking()
.last();
fail("Should throw a TestException when the observableFactory throws it");
} catch (TestException e) {
// Make sure that unsubscribe is called so that users can close
// the resource if some error happens.
verify(unsubscribe, times(1)).call();
}
}
@Test
public void testUsingWithObservableFactoryErrorInOnSubscribe() {
performTestUsingWithObservableFactoryErrorInOnSubscribe(false);
}
@Test
public void testUsingWithObservableFactoryErrorInOnSubscribeDisposeEagerly() {
performTestUsingWithObservableFactoryErrorInOnSubscribe(true);
}
private void performTestUsingWithObservableFactoryErrorInOnSubscribe(boolean disposeEagerly) {
final Action0 unsubscribe = mock(Action0.class);
Func0<Subscription> resourceFactory = new Func0<Subscription>() {
@Override
public Subscription call() {
return Subscriptions.create(unsubscribe);
}
};
Func1<Subscription, Observable<Integer>> observableFactory = new Func1<Subscription, Observable<Integer>>() {
@Override
public Observable<Integer> call(Subscription subscription) {
return Observable.create(new OnSubscribe<Integer>() {
@Override
public void call(Subscriber<? super Integer> t1) {
throw new TestException();
}
});
}
};
try {
Observable
.using(resourceFactory, observableFactory, disposeSubscription, disposeEagerly)
.toBlocking().last();
fail("Should throw a TestException when the observableFactory throws it");
} catch (TestException e) {
// Make sure that unsubscribe is called so that users can close
// the resource if some error happens.
verify(unsubscribe, times(1)).call();
}
}
@Test
public void testUsingDisposesEagerlyBeforeCompletion() {
final List<String> events = new ArrayList<String>();
Func0<Resource> resourceFactory = createResourceFactory(events);
final Action0 completion = createOnCompletedAction(events);
final Action0 unsub =createUnsubAction(events);
Func1<Resource, Observable<String>> observableFactory = new Func1<Resource, Observable<String>>() {
@Override
public Observable<String> call(Resource resource) {
return Observable.from(resource.getTextFromWeb().split(" "));
}
};
@SuppressWarnings("unchecked")
Observer<String> observer = mock(Observer.class);
Observable<String> observable = Observable.using(resourceFactory, observableFactory,
new DisposeAction(), true).doOnUnsubscribe(unsub)
.doOnCompleted(completion);
observable.subscribe(observer);
assertEquals(Arrays.asList("disposed", "completed", "unsub"), events);
}
@Test
public void testUsingDoesNotDisposesEagerlyBeforeCompletion() {
final List<String> events = new ArrayList<String>();
Func0<Resource> resourceFactory = createResourceFactory(events);
final Action0 completion = createOnCompletedAction(events);
final Action0 unsub =createUnsubAction(events);
Func1<Resource, Observable<String>> observableFactory = new Func1<Resource, Observable<String>>() {
@Override
public Observable<String> call(Resource resource) {
return Observable.from(resource.getTextFromWeb().split(" "));
}
};
@SuppressWarnings("unchecked")
Observer<String> observer = mock(Observer.class);
Observable<String> observable = Observable.using(resourceFactory, observableFactory,
new DisposeAction(), false).doOnUnsubscribe(unsub)
.doOnCompleted(completion);
observable.subscribe(observer);
assertEquals(Arrays.asList("completed", "unsub", "disposed"), events);
}
@Test
public void testUsingDisposesEagerlyBeforeError() {
final List<String> events = new ArrayList<String>();
Func0<Resource> resourceFactory = createResourceFactory(events);
final Action1<Throwable> onError = createOnErrorAction(events);
final Action0 unsub = createUnsubAction(events);
Func1<Resource, Observable<String>> observableFactory = new Func1<Resource, Observable<String>>() {
@Override
public Observable<String> call(Resource resource) {
return Observable.from(resource.getTextFromWeb().split(" ")).concatWith(Observable.<String>error(new RuntimeException()));
}
};
@SuppressWarnings("unchecked")
Observer<String> observer = mock(Observer.class);
Observable<String> observable = Observable.using(resourceFactory, observableFactory,
new DisposeAction(), true).doOnUnsubscribe(unsub)
.doOnError(onError);
observable.subscribe(observer);
assertEquals(Arrays.asList("disposed", "error", "unsub"), events);
}
@Test
public void testUsingDoesNotDisposesEagerlyBeforeError() {
final List<String> events = new ArrayList<String>();
Func0<Resource> resourceFactory = createResourceFactory(events);
final Action1<Throwable> onError = createOnErrorAction(events);
final Action0 unsub = createUnsubAction(events);
Func1<Resource, Observable<String>> observableFactory = new Func1<Resource, Observable<String>>() {
@Override
public Observable<String> call(Resource resource) {
return Observable.from(resource.getTextFromWeb().split(" ")).concatWith(Observable.<String>error(new RuntimeException()));
}
};
@SuppressWarnings("unchecked")
Observer<String> observer = mock(Observer.class);
Observable<String> observable = Observable.using(resourceFactory, observableFactory,
new DisposeAction(), false).doOnUnsubscribe(unsub)
.doOnError(onError);
observable.subscribe(observer);
assertEquals(Arrays.asList("error", "unsub", "disposed"), events);
}
private static Action0 createUnsubAction(final List<String> events) {
return new Action0() {
@Override
public void call() {
events.add("unsub");
}
};
}
private static Action1<Throwable> createOnErrorAction(final List<String> events) {
return new Action1<Throwable>() {
@Override
public void call(Throwable t) {
events.add("error");
}
};
}
private static Func0<Resource> createResourceFactory(final List<String> events) {
return new Func0<Resource>() {
@Override
public Resource call() {
return new Resource() {
@Override
public String getTextFromWeb() {
return "hello world";
}
@Override
public void dispose() {
events.add("disposed");
}
};
}
};
}
private static Action0 createOnCompletedAction(final List<String> events) {
return new Action0() {
@Override
public void call() {
events.add("completed");
}
};
}
}