package com.apollographql.apollo; import com.apollographql.android.impl.normalizer.EpisodeHeroName; import com.apollographql.android.impl.normalizer.HeroAndFriendsNamesWithIDs; import com.apollographql.android.impl.normalizer.type.Episode; import com.apollographql.apollo.api.Response; import com.apollographql.apollo.cache.normalized.CacheControl; import com.apollographql.apollo.cache.normalized.lru.EvictionPolicy; import com.apollographql.apollo.cache.normalized.lru.LruNormalizedCacheFactory; import com.apollographql.apollo.exception.ApolloException; import com.apollographql.apollo.rx2.Rx2Apollo; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import io.reactivex.disposables.Disposable; import io.reactivex.functions.Function; import io.reactivex.observers.DisposableObserver; import io.reactivex.observers.TestObserver; import okhttp3.OkHttpClient; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import static com.google.common.truth.Truth.assertThat; public class Rx2ApolloTest { private ApolloClient apolloClient; private MockWebServer mockWebServer; private static final int RX_DELAY_SECONDS = 2; private static final long TIME_OUT_SECONDS = 3; private static final String FILE_EPISODE_HERO_NAME_WITH_ID = "EpisodeHeroNameResponseWithId.json"; private static final String FILE_EPISODE_HERO_NAME_CHANGE = "EpisodeHeroNameResponseNameChange.json"; @Before public void setup() { mockWebServer = new MockWebServer(); OkHttpClient okHttpClient = new OkHttpClient.Builder().build(); apolloClient = ApolloClient.builder() .serverUrl(mockWebServer.url("/")) .okHttpClient(okHttpClient) .normalizedCache(new LruNormalizedCacheFactory(EvictionPolicy.NO_EVICTION), new IdFieldCacheKeyResolver()) .build(); } @After public void tearDown() { try { mockWebServer.shutdown(); } catch (IOException ignore) { //ignore } } @Test public void testRx2CallProducesValue() throws IOException, InterruptedException { EpisodeHeroName query = EpisodeHeroName.builder().episode(Episode.EMPIRE).build(); mockWebServer.enqueue(mockResponse(FILE_EPISODE_HERO_NAME_WITH_ID)); EpisodeHeroName.Data data = Rx2Apollo .from(apolloClient.query(query)) .test() .await() .assertNoErrors() .assertComplete() .values() .get(0) .data(); assertThat(data.hero().name()).isEqualTo("R2-D2"); } @Test public void testRx2CallIsCanceledWhenDisposed() throws IOException { EpisodeHeroName query = EpisodeHeroName.builder().episode(Episode.EMPIRE).build(); mockWebServer.enqueue(mockResponse(FILE_EPISODE_HERO_NAME_WITH_ID)); TestObserver<Response<EpisodeHeroName.Data>> testObserver = new TestObserver<>(); Disposable disposable = Rx2Apollo .from(apolloClient.query(query)) .delay(5, TimeUnit.SECONDS) .subscribeWith(testObserver); disposable.dispose(); assertThat(testObserver.isDisposed()).isTrue(); testObserver.assertNoValues(); } @Test public void testRx2PrefetchCompletes() throws IOException, InterruptedException { EpisodeHeroName query = EpisodeHeroName.builder().episode(Episode.EMPIRE).build(); mockWebServer.enqueue(mockResponse(FILE_EPISODE_HERO_NAME_WITH_ID)); Rx2Apollo .from(apolloClient.prefetch(query)) .test() .await() .assertNoErrors() .assertComplete(); } @Test public void testRx2PrefetchIsCanceledWhenDisposed() throws IOException { EpisodeHeroName query = EpisodeHeroName.builder().episode(Episode.EMPIRE).build(); mockWebServer.enqueue(mockResponse(FILE_EPISODE_HERO_NAME_WITH_ID)); TestObserver<EpisodeHeroName.Data> testObserver = new TestObserver<>(); Disposable disposable = Rx2Apollo .from(apolloClient.prefetch(query)) .delay(RX_DELAY_SECONDS, TimeUnit.SECONDS) .subscribeWith(testObserver); disposable.dispose(); assertThat(testObserver.isDisposed()).isTrue(); testObserver.assertNotComplete(); } @Test public void testRx2QueryWatcherUpdated_SameQuery_DifferentResults() throws IOException, TimeoutException, InterruptedException, ApolloException { final NamedCountDownLatch firstResponseLatch = new NamedCountDownLatch("firstResponseLatch", 1); final NamedCountDownLatch secondResponseLatch = new NamedCountDownLatch("secondResponseLatch", 2); EpisodeHeroName query = EpisodeHeroName.builder().episode(Episode.EMPIRE).build(); mockWebServer.enqueue(mockResponse(FILE_EPISODE_HERO_NAME_WITH_ID)); ApolloQueryWatcher<EpisodeHeroName.Data> watcher = apolloClient.query(query).watcher(); Rx2Apollo .from(watcher) .map(new Function<Response<EpisodeHeroName.Data>, EpisodeHeroName.Data>() { @Override public EpisodeHeroName.Data apply(Response<EpisodeHeroName.Data> response) throws Exception { return response.data(); } }) .subscribe(new DisposableObserver<EpisodeHeroName.Data>() { @Override public void onNext(EpisodeHeroName.Data data) { if (secondResponseLatch.getCount() == 2) { assertThat(data.hero().name()).isEqualTo("R2-D2"); } else if (secondResponseLatch.getCount() == 1) { assertThat(data.hero().name()).isEqualTo("Artoo"); } firstResponseLatch.countDown(); secondResponseLatch.countDown(); } @Override public void onError(Throwable e) { Assert.fail(e.getMessage()); firstResponseLatch.countDown(); secondResponseLatch.countDown(); } @Override public void onComplete() { } }); firstResponseLatch.awaitOrThrowWithTimeout(TIME_OUT_SECONDS, TimeUnit.SECONDS); //Another newer call gets updated information mockWebServer.enqueue(mockResponse(FILE_EPISODE_HERO_NAME_CHANGE)); apolloClient.query(query).cacheControl(CacheControl.NETWORK_ONLY).execute(); secondResponseLatch.awaitOrThrowWithTimeout(TIME_OUT_SECONDS, TimeUnit.SECONDS); } @Test public void testRx2QueryWatcherNotUpdated_SameQuery_SameResults() throws IOException, TimeoutException, InterruptedException, ApolloException { final NamedCountDownLatch firstResponseLatch = new NamedCountDownLatch("firstResultLatch", 1); final NamedCountDownLatch secondResponseLatch = new NamedCountDownLatch("secondResultLatch", 2); EpisodeHeroName query = EpisodeHeroName.builder().episode(Episode.EMPIRE).build(); mockWebServer.enqueue(mockResponse(FILE_EPISODE_HERO_NAME_WITH_ID)); ApolloQueryWatcher<EpisodeHeroName.Data> watcher = apolloClient.query(query).watcher(); Rx2Apollo .from(watcher) .map(new Function<Response<EpisodeHeroName.Data>, EpisodeHeroName.Data>() { @Override public EpisodeHeroName.Data apply(Response<EpisodeHeroName.Data> response) throws Exception { return response.data(); } }) .subscribe(new DisposableObserver<EpisodeHeroName.Data>() { @Override public void onNext(EpisodeHeroName.Data data) { assertThat(data.hero().name()).isEqualTo("R2-D2"); firstResponseLatch.countDown(); secondResponseLatch.countDown(); if (secondResponseLatch.getCount() == 0) { Assert.fail("received two callbacks, although data shouldn't change"); } } @Override public void onError(Throwable e) { Assert.fail(e.getMessage()); firstResponseLatch.countDown(); secondResponseLatch.countDown(); } @Override public void onComplete() { } }); firstResponseLatch.awaitOrThrowWithTimeout(TIME_OUT_SECONDS, TimeUnit.SECONDS); mockWebServer.enqueue(mockResponse(FILE_EPISODE_HERO_NAME_WITH_ID)); apolloClient.query(query).cacheControl(CacheControl.NETWORK_ONLY).enqueue(null); secondResponseLatch.await(TIME_OUT_SECONDS, TimeUnit.SECONDS); } @Test public void testRx2QueryWatcherUpdated_DifferentQuery_DifferentResults() throws IOException, InterruptedException, TimeoutException, ApolloException { final NamedCountDownLatch firstResponseLatch = new NamedCountDownLatch("firstResponseLatch", 1); final NamedCountDownLatch secondResponseLatch = new NamedCountDownLatch("secondResponseLatch", 2); mockWebServer.enqueue(mockResponse(FILE_EPISODE_HERO_NAME_WITH_ID)); EpisodeHeroName query = EpisodeHeroName.builder().episode(Episode.EMPIRE).build(); ApolloQueryWatcher<EpisodeHeroName.Data> watcher = apolloClient.query(query).watcher(); Rx2Apollo .from(watcher) .map(new Function<Response<EpisodeHeroName.Data>, EpisodeHeroName.Data>() { @Override public EpisodeHeroName.Data apply(Response<EpisodeHeroName.Data> response) throws Exception { return response.data(); } }) .subscribe(new DisposableObserver<EpisodeHeroName.Data>() { @Override public void onNext(EpisodeHeroName.Data data) { if (secondResponseLatch.getCount() == 2) { assertThat(data.hero().name()).isEqualTo("R2-D2"); } else if (secondResponseLatch.getCount() == 1) { assertThat(data.hero().name()).isEqualTo("Artoo"); } firstResponseLatch.countDown(); secondResponseLatch.countDown(); } @Override public void onComplete() { } @Override public void onError(Throwable e) { Assert.fail(e.getMessage()); firstResponseLatch.countDown(); secondResponseLatch.countDown(); } }); firstResponseLatch.awaitOrThrowWithTimeout(TIME_OUT_SECONDS, TimeUnit.SECONDS); HeroAndFriendsNamesWithIDs friendsQuery = HeroAndFriendsNamesWithIDs.builder().episode(Episode.NEWHOPE).build(); mockWebServer.enqueue(mockResponse("HeroAndFriendsNameWithIdsNameChange.json")); apolloClient.query(friendsQuery).cacheControl(CacheControl.NETWORK_ONLY).execute(); secondResponseLatch.awaitOrThrowWithTimeout(TIME_OUT_SECONDS, TimeUnit.SECONDS); } @Test public void testRx2QueryWatcherNotCalled_WhenCanceled() throws IOException, TimeoutException, InterruptedException, ApolloException { final NamedCountDownLatch firstResponseLatch = new NamedCountDownLatch("firstResponseLatch", 1); final NamedCountDownLatch secondResponseLatch = new NamedCountDownLatch("secondResponseLatch", 2); EpisodeHeroName query = EpisodeHeroName.builder().episode(Episode.EMPIRE).build(); mockWebServer.enqueue(mockResponse(FILE_EPISODE_HERO_NAME_WITH_ID)); ApolloQueryWatcher<EpisodeHeroName.Data> watcher = apolloClient.query(query).watcher(); Disposable disposable = Rx2Apollo .from(watcher) .map(new Function<Response<EpisodeHeroName.Data>, EpisodeHeroName.Data>() { @Override public EpisodeHeroName.Data apply(Response<EpisodeHeroName.Data> response) throws Exception { return response.data(); } }) .subscribeWith(new DisposableObserver<EpisodeHeroName.Data>() { @Override public void onNext(EpisodeHeroName.Data data) { assertThat(data.hero().name()).isEqualTo("R2-D2"); firstResponseLatch.countDown(); secondResponseLatch.countDown(); if (secondResponseLatch.getCount() == 0) { Assert.fail("Received two onNext, although RxQueryWatcher has already been canceled"); } } @Override public void onError(Throwable e) { Assert.fail(e.getMessage()); firstResponseLatch.countDown(); secondResponseLatch.countDown(); } @Override public void onComplete() { } }); firstResponseLatch.awaitOrThrowWithTimeout(TIME_OUT_SECONDS, TimeUnit.SECONDS); mockWebServer.enqueue(mockResponse(FILE_EPISODE_HERO_NAME_CHANGE)); disposable.dispose(); apolloClient.query(query).cacheControl(CacheControl.NETWORK_ONLY).execute(); secondResponseLatch.await(TIME_OUT_SECONDS, TimeUnit.SECONDS); } private MockResponse mockResponse(String fileName) throws IOException { return new MockResponse().setChunkedBody(Utils.readFileToString(getClass(), "/" + fileName), 32); } }