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.rx.RxApollo; import junit.framework.Assert; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import okhttp3.OkHttpClient; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import rx.Observer; import rx.Subscription; import rx.observers.AssertableSubscriber; import rx.observers.TestSubscriber; import static com.google.common.truth.Truth.assertThat; public class RxApolloTest { private ApolloClient apolloClient; private MockWebServer server; private static final int RX_DELAY_SECONDS = 2; private static final long TIME_OUT_SECONDS = 3; @Before public void setUp() { server = new MockWebServer(); OkHttpClient okHttpClient = new OkHttpClient.Builder().build(); apolloClient = ApolloClient.builder() .serverUrl(server.url("/")) .okHttpClient(okHttpClient) .normalizedCache(new LruNormalizedCacheFactory(EvictionPolicy.NO_EVICTION), new IdFieldCacheKeyResolver()) .build(); } @After public void tearDown() { try { server.shutdown(); } catch (IOException ignored) { } } @Test public void testRxCallProducesValue() throws IOException { EpisodeHeroName query = EpisodeHeroName.builder().episode(Episode.EMPIRE).build(); server.enqueue(mockResponse("EpisodeHeroNameResponseWithId.json")); Response<EpisodeHeroName.Data> response = RxApollo .from(apolloClient.query(query)) .test() .awaitTerminalEvent() .assertNoErrors() .assertCompleted() .getOnNextEvents() .get(0); assertThat(response.data().hero().name()).isEqualTo("R2-D2"); } @Test public void textRxCallIsCancelledWhenUnsubscribed() throws IOException { EpisodeHeroName query = EpisodeHeroName.builder().episode(Episode.EMPIRE).build(); server.enqueue(mockResponse("EpisodeHeroNameResponseWithId.json")); ApolloCall<EpisodeHeroName.Data> call = apolloClient.query(query); TestSubscriber<Response<EpisodeHeroName.Data>> subscriber = new TestSubscriber<>(); Subscription subscription = RxApollo .from(call) .delay(RX_DELAY_SECONDS, TimeUnit.SECONDS) .subscribe(subscriber); subscription.unsubscribe(); subscriber .assertUnsubscribed(); subscriber.assertNoValues(); } @Test public void testRxPrefetchCompletes() throws IOException { EpisodeHeroName query = EpisodeHeroName.builder().episode(Episode.EMPIRE).build(); server.enqueue(mockResponse("EpisodeHeroNameResponseWithId.json")); RxApollo .from(apolloClient.prefetch(query)) .test() .awaitTerminalEvent() .assertNoErrors() .assertCompleted(); } @Test public void testRxPrefetchIsCanceledWhenUnsubscribed() throws IOException { EpisodeHeroName query = EpisodeHeroName.builder().episode(Episode.EMPIRE).build(); server.enqueue(mockResponse("EpisodeHeroNameResponseWithId.json")); ApolloPrefetch prefetch = apolloClient.prefetch(query); AssertableSubscriber<Void> subscriber = RxApollo .from(prefetch) .delay(RX_DELAY_SECONDS, TimeUnit.SECONDS) .test(); subscriber.unsubscribe(); subscriber.assertUnsubscribed(); subscriber.assertNotCompleted(); } @Test public void testRxQueryWatcherUpdated_SameQuery_DifferentResults() throws IOException, InterruptedException, TimeoutException, ApolloException { final NamedCountDownLatch firstResponseLatch = new NamedCountDownLatch("firstResponseLatch", 1); final NamedCountDownLatch secondResponseLatch = new NamedCountDownLatch("secondResponseLatch", 2); EpisodeHeroName query = EpisodeHeroName.builder().episode(Episode.EMPIRE).build(); server.enqueue(mockResponse("EpisodeHeroNameResponseWithId.json")); ApolloQueryWatcher<EpisodeHeroName.Data> watcher = apolloClient.query(query).watcher(); RxApollo.from(watcher).subscribe(new Observer<Response<EpisodeHeroName.Data>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { Assert.fail(e.getMessage()); firstResponseLatch.countDown(); secondResponseLatch.countDown(); } @Override public void onNext(Response<EpisodeHeroName.Data> response) { if (secondResponseLatch.getCount() == 2) { assertThat(response.data().hero().name()).isEqualTo("R2-D2"); } else if (secondResponseLatch.getCount() == 1) { assertThat(response.data().hero().name()).isEqualTo("Artoo"); } firstResponseLatch.countDown(); secondResponseLatch.countDown(); } }); firstResponseLatch.awaitOrThrowWithTimeout(TIME_OUT_SECONDS, TimeUnit.SECONDS); //Another newer call gets updated information server.enqueue(mockResponse("EpisodeHeroNameResponseNameChange.json")); apolloClient.query(query).cacheControl(CacheControl.NETWORK_ONLY).execute(); secondResponseLatch.awaitOrThrowWithTimeout(TIME_OUT_SECONDS, TimeUnit.SECONDS); } @Test public void testRxQueryWatcherNotUpdated_SameQuery_SameResults() throws IOException, InterruptedException, TimeoutException { final NamedCountDownLatch firstResponseLatch = new NamedCountDownLatch("firstResponseLatch", 1); final NamedCountDownLatch secondResponseLatch = new NamedCountDownLatch("secondResponseLatch", 2); EpisodeHeroName query = EpisodeHeroName.builder().episode(Episode.EMPIRE).build(); server.enqueue(mockResponse("EpisodeHeroNameResponseWithId.json")); ApolloQueryWatcher<EpisodeHeroName.Data> watcher = apolloClient.query(query).watcher(); RxApollo.from(watcher).subscribe(new Observer<Response<EpisodeHeroName.Data>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { Assert.fail(e.getMessage()); firstResponseLatch.countDown(); secondResponseLatch.countDown(); } @Override public void onNext(Response<EpisodeHeroName.Data> response) { assertThat(response.data().hero().name()).isEqualTo("R2-D2"); firstResponseLatch.countDown(); secondResponseLatch.countDown(); if (secondResponseLatch.getCount() == 0) { Assert.fail("Received two callbacks, although data should not have changed."); } } }); firstResponseLatch.awaitOrThrowWithTimeout(TIME_OUT_SECONDS, TimeUnit.SECONDS); server.enqueue(mockResponse("EpisodeHeroNameResponseWithId.json")); apolloClient.query(query).cacheControl(CacheControl.NETWORK_ONLY).enqueue(null); // Wait 3 seconds to make sure no double callback. // Successful if timeout _is_ reached secondResponseLatch.await(3, TimeUnit.SECONDS); } @Test public void testQueryRxWatcherUpdated_DifferentQuery_DifferentResults() throws IOException, InterruptedException, TimeoutException, ApolloException { final NamedCountDownLatch firstResponseLatch = new NamedCountDownLatch("firstResponseLatch", 1); final NamedCountDownLatch secondResponseLatch = new NamedCountDownLatch("secondResponseLatch", 2); server.enqueue(mockResponse("EpisodeHeroNameResponseWithId.json")); EpisodeHeroName query = EpisodeHeroName.builder().episode(Episode.EMPIRE).build(); ApolloQueryWatcher<EpisodeHeroName.Data> watcher = apolloClient.query(query).watcher(); RxApollo.from(watcher).subscribe(new Observer<Response<EpisodeHeroName.Data>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { Assert.fail(e.getMessage()); firstResponseLatch.countDown(); secondResponseLatch.countDown(); } @Override public void onNext(Response<EpisodeHeroName.Data> response) { if (secondResponseLatch.getCount() == 2) { assertThat(response.data().hero().name()).isEqualTo("R2-D2"); } else if (secondResponseLatch.getCount() == 1) { assertThat(response.data().hero().name()).isEqualTo("Artoo"); } firstResponseLatch.countDown(); secondResponseLatch.countDown(); } }); firstResponseLatch.awaitOrThrowWithTimeout(TIME_OUT_SECONDS, TimeUnit.SECONDS); HeroAndFriendsNamesWithIDs friendsQuery = HeroAndFriendsNamesWithIDs.builder().episode(Episode.NEWHOPE).build(); server.enqueue(mockResponse("HeroAndFriendsNameWithIdsNameChange.json")); apolloClient.query(friendsQuery).cacheControl(CacheControl.NETWORK_ONLY).execute(); secondResponseLatch.awaitOrThrowWithTimeout(TIME_OUT_SECONDS, TimeUnit.SECONDS); } private MockResponse mockResponse(String fileName) throws IOException { return new MockResponse().setChunkedBody(Utils.readFileToString(getClass(), "/" + fileName), 32); } }