Filling the gap between RxJava and Espresso
compile (name: 'rxespresso-1.0', ext: 'aar')
or if you only use it in Espresso tests:
androidTestCompile (name: 'rxespresso-1.0', ext: 'aar')
-
Set the global log level:
RxEspresso.setLogLevel(LogLevel.DEBUG);
-
Increment and decrement the counter based on your design. The flexibility of this library means that you decide which Observable chains Espresso should wait for and which it should not. We chose to increment onSubscribe and decrement afterTerminate
dataStore.getData() .subscribeOn(Schedulers.computation()) .observeOn(AndroidSchedulers.mainThread()) .doOnSubscribe(() -> RxEspresso.increment()) .doAfterTerminate(() -> RxEspresso.decrement()) .subscribe(// on Next);
-
Monitor the idle state. This is optional but RxEspresso exposes an
isIdleNow
method to track idle state. This is helpful in ensuring that monitored Observables have completed before a new test begins. Since any open streams after one test will leave the app in an indeterminate state for the next test, we chose to check idle state between tests and fail the whole suite if not idle:public class BaseTest { @After public void tearDown() throws Exception { // if there is anything still idling then future tests may fail boolean idleNow = RxEspresso.isIdleNow(); if (!idleNow) { String msg = "Test is over but RxEspresso is not idle. " + "Remaining tests may fail unexpectedly."; Log.e("TESTING", msg); System.exit(-1); } } }
Since doOnSubscribe
and doAfterTerminate
are always used together, we follow Dan Lew's pattern of using a Transformer to better compose observable chains. We bundle doOnSubscribe
and doAfterTerminate
:
public final class RxEspressoTransformer{
private final Observable.Transformer transformer;
public RxEspressoTransformer() {
transformer = observable -> ((Observable) observable)
.doOnSubscribe(() -> RxEspresso.increment())
.doAfterTerminate(() -> RxEspresso.decrement());
}
public <T> Observable.Transformer<T, T> apply() {
return (Observable.Transformer<T, T>) transformer;
}
}
and then apply our Transformer in code:
RxEspressoTransformer rxEspressoTransformer = new RxEspressoTransformer();
dataStore.getData()
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.compose(rxEspressoTransformer.apply())
.subscribe(// on Next);
However we are still leaking test code into production. Instead we define a UiSchedulersTransformer
interface and using dependency injection, supply production and test implementations. Only within the test implementation do we call into RxEspresso:
public interface UiSchedulersTransformer {
<T> Observable.Transformer<T, T> apply();
}
public final class ProductionUiSchedulersTransformer implements UiSchedulersTransformer {
private final Observable.Transformer schedulersTransformer;
public ProductionUiSchedulersTransformer() {
schedulersTransformer = observable -> ((Observable) observable)
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread());
}
@Override
public <T> Observable.Transformer<T, T> apply() {
return (Observable.Transformer<T, T>) schedulersTransformer;
}
}
public final class TestingUiSchedulersTransformer implements UiSchedulersTransformer {
private final Observable.Transformer schedulersTransformer;
public RxEspressoTransformer() {
schedulersTransformer = observable -> ((Observable) observable)
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(() -> RxEspresso.increment())
.doAfterTerminate(() -> RxEspresso.decrement());
}
@Override
public <T> Observable.Transformer<T, T> apply() {
return (Observable.Transformer<T, T>) schedulersTransformer;
}
}
Then in code we call our injected instance of UiSchedulersTransformer
:
@Inject UiSchedulersTransformer uiSchedulersTransformer;
dataStore.getData()
.compose(uiSchedulersTransformer.apply())
.subscribe(// on Next);