/* * Copyright (C) 2013 Square, 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 retrofit2; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.annotation.Retention; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import okhttp3.HttpUrl; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.ResponseBody; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import org.junit.Rule; import org.junit.Test; import retrofit2.helpers.DelegatingCallAdapterFactory; import retrofit2.helpers.NonMatchingCallAdapterFactory; import retrofit2.helpers.NonMatchingConverterFactory; import retrofit2.helpers.ToStringConverterFactory; import retrofit2.http.Body; import retrofit2.http.GET; import retrofit2.http.POST; import retrofit2.http.Query; import static java.lang.annotation.RetentionPolicy.RUNTIME; import static okhttp3.mockwebserver.SocketPolicy.DISCONNECT_AT_START; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; public final class RetrofitTest { @Rule public final MockWebServer server = new MockWebServer(); interface CallMethod { @GET("/") Call<String> disallowed(); @POST("/") Call<ResponseBody> disallowed(@Body String body); @GET("/") Call<retrofit2.Response> badType1(); @GET("/") Call<okhttp3.Response> badType2(); @GET("/") Call<ResponseBody> getResponseBody(); @GET("/") Call<Void> getVoid(); @POST("/") Call<ResponseBody> postRequestBody(@Body RequestBody body); @GET("/") Call<ResponseBody> queryString(@Query("foo") String foo); @GET("/") Call<ResponseBody> queryObject(@Query("foo") Object foo); } interface FutureMethod { @GET("/") Future<String> method(); } interface Extending extends CallMethod { } interface StringService { @GET("/") String get(); } interface UnresolvableResponseType { @GET("/") <T> Call<T> typeVariable(); @GET("/") <T extends ResponseBody> Call<T> typeVariableUpperBound(); @GET("/") <T> Call<List<Map<String, Set<T[]>>>> crazy(); @GET("/") Call<?> wildcard(); @GET("/") Call<? extends ResponseBody> wildcardUpperBound(); } interface UnresolvableParameterType { @POST("/") <T> Call<ResponseBody> typeVariable(@Body T body); @POST("/") <T extends RequestBody> Call<ResponseBody> typeVariableUpperBound(@Body T body); @POST("/") <T> Call<ResponseBody> crazy(@Body List<Map<String, Set<T[]>>> body); @POST("/") Call<ResponseBody> wildcard(@Body List<?> body); @POST("/") Call<ResponseBody> wildcardUpperBound(@Body List<? extends RequestBody> body); } interface VoidService { @GET("/") void nope(); } interface Annotated { @GET("/") @Foo Call<String> method(); @POST("/") Call<ResponseBody> bodyParameter(@Foo @Body String param); @GET("/") Call<ResponseBody> queryParameter(@Foo @Query("foo") Object foo); @Retention(RUNTIME) @interface Foo {} } interface MutableParameters { @GET("/") Call<String> method(@Query("i") AtomicInteger value); } @SuppressWarnings("EqualsBetweenInconvertibleTypes") // We are explicitly testing this behavior. @Test public void objectMethodsStillWork() { Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .build(); CallMethod example = retrofit.create(CallMethod.class); assertThat(example.hashCode()).isNotZero(); assertThat(example.equals(this)).isFalse(); assertThat(example.toString()).isNotEmpty(); } @Test public void interfaceWithExtendIsNotSupported() { Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .build(); try { retrofit.create(Extending.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("API interfaces must not extend other interfaces."); } } @Test public void cloneSharesStatefulInstances() { CallAdapter.Factory callAdapter = mock(CallAdapter.Factory.class); Converter.Factory converter = mock(Converter.Factory.class); HttpUrl baseUrl = server.url("/"); Executor executor = mock(Executor.class); okhttp3.Call.Factory callFactory = mock(okhttp3.Call.Factory.class); Retrofit one = new Retrofit.Builder() .addCallAdapterFactory(callAdapter) .addConverterFactory(converter) .baseUrl(baseUrl) .callbackExecutor(executor) .callFactory(callFactory) .build(); CallAdapter.Factory callAdapter2 = mock(CallAdapter.Factory.class); Converter.Factory converter2 = mock(Converter.Factory.class); Retrofit two = one.newBuilder() .addCallAdapterFactory(callAdapter2) .addConverterFactory(converter2) .build(); assertEquals(one.callAdapterFactories().size() + 1, two.callAdapterFactories().size()); assertThat(two.callAdapterFactories()).contains(callAdapter, callAdapter2); assertEquals(one.converterFactories().size() + 1, two.converterFactories().size()); assertThat(two.converterFactories()).contains(converter, converter2); assertSame(baseUrl, two.baseUrl()); assertSame(executor, two.callbackExecutor()); assertSame(callFactory, two.callFactory()); } @Test public void responseTypeCannotBeRetrofitResponse() { Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .build(); CallMethod service = retrofit.create(CallMethod.class); try { service.badType1(); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "'retrofit2.Response' is not a valid response body type. Did you mean ResponseBody?\n" + " for method CallMethod.badType1"); } } @Test public void responseTypeCannotBeOkHttpResponse() { Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .build(); CallMethod service = retrofit.create(CallMethod.class); try { service.badType2(); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage( "'okhttp3.Response' is not a valid response body type. Did you mean ResponseBody?\n" + " for method CallMethod.badType2"); } } @Test public void voidReturnTypeNotAllowed() { Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .build(); VoidService service = retrofit.create(VoidService.class); try { service.nope(); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessageStartingWith( "Service methods cannot return void.\n for method VoidService.nope"); } } @Test public void validateEagerlyDisabledByDefault() { Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .build(); // Should not throw exception about incorrect configuration of the VoidService retrofit.create(VoidService.class); } @Test public void validateEagerlyDisabledByUser() { Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .validateEagerly(false) .build(); // Should not throw exception about incorrect configuration of the VoidService retrofit.create(VoidService.class); } @Test public void validateEagerlyFailsAtCreation() { Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .validateEagerly(true) .build(); try { retrofit.create(VoidService.class); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessageStartingWith( "Service methods cannot return void.\n for method VoidService.nope"); } } @Test public void callCallAdapterAddedByDefault() { Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .build(); CallMethod example = retrofit.create(CallMethod.class); assertThat(example.getResponseBody()).isNotNull(); } @Test public void callCallCustomAdapter() { final AtomicBoolean factoryCalled = new AtomicBoolean(); final AtomicBoolean adapterCalled = new AtomicBoolean(); class MyCallAdapterFactory extends CallAdapter.Factory { @Override public CallAdapter<?, ?> get(final Type returnType, Annotation[] annotations, Retrofit retrofit) { factoryCalled.set(true); if (getRawType(returnType) != Call.class) { return null; } return new CallAdapter<Object, Call<?>>() { @Override public Type responseType() { return getParameterUpperBound(0, (ParameterizedType) returnType); } @Override public Call<Object> adapt(Call<Object> call) { adapterCalled.set(true); return call; } }; } } Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .addCallAdapterFactory(new MyCallAdapterFactory()) .build(); CallMethod example = retrofit.create(CallMethod.class); assertThat(example.getResponseBody()).isNotNull(); assertThat(factoryCalled.get()).isTrue(); assertThat(adapterCalled.get()).isTrue(); } @Test public void customCallAdapter() { class GreetingCallAdapterFactory extends CallAdapter.Factory { @Override public CallAdapter<Object, String> get(Type returnType, Annotation[] annotations, Retrofit retrofit) { if (getRawType(returnType) != String.class) { return null; } return new CallAdapter<Object, String>() { @Override public Type responseType() { return String.class; } @Override public String adapt(Call<Object> call) { return "Hi!"; } }; } } Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .addConverterFactory(new ToStringConverterFactory()) .addCallAdapterFactory(new GreetingCallAdapterFactory()) .build(); StringService example = retrofit.create(StringService.class); assertThat(example.get()).isEqualTo("Hi!"); } @Test public void methodAnnotationsPassedToCallAdapter() { final AtomicReference<Annotation[]> annotationsRef = new AtomicReference<>(); class MyCallAdapterFactory extends CallAdapter.Factory { @Override public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) { annotationsRef.set(annotations); return null; } } Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .addConverterFactory(new ToStringConverterFactory()) .addCallAdapterFactory(new MyCallAdapterFactory()) .build(); Annotated annotated = retrofit.create(Annotated.class); annotated.method(); // Trigger internal setup. Annotation[] annotations = annotationsRef.get(); assertThat(annotations).hasAtLeastOneElementOfType(Annotated.Foo.class); } @Test public void customCallAdapterMissingThrows() { Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .build(); FutureMethod example = retrofit.create(FutureMethod.class); try { example.method(); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("" + "Unable to create call adapter for java.util.concurrent.Future<java.lang.String>\n" + " for method FutureMethod.method"); assertThat(e.getCause()).hasMessage("" + "Could not locate call adapter for java.util.concurrent.Future<java.lang.String>.\n" + " Tried:\n" + " * retrofit2.DefaultCallAdapterFactory"); } } @Test public void methodAnnotationsPassedToResponseBodyConverter() { final AtomicReference<Annotation[]> annotationsRef = new AtomicReference<>(); class MyConverterFactory extends Converter.Factory { @Override public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { annotationsRef.set(annotations); return new ToStringConverterFactory().responseBodyConverter(type, annotations, retrofit); } } Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .addConverterFactory(new MyConverterFactory()) .build(); Annotated annotated = retrofit.create(Annotated.class); annotated.method(); // Trigger internal setup. Annotation[] annotations = annotationsRef.get(); assertThat(annotations).hasAtLeastOneElementOfType(Annotated.Foo.class); } @Test public void methodAndParameterAnnotationsPassedToRequestBodyConverter() { final AtomicReference<Annotation[]> parameterAnnotationsRef = new AtomicReference<>(); final AtomicReference<Annotation[]> methodAnnotationsRef = new AtomicReference<>(); class MyConverterFactory extends Converter.Factory { @Override public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { parameterAnnotationsRef.set(parameterAnnotations); methodAnnotationsRef.set(methodAnnotations); return new ToStringConverterFactory().requestBodyConverter(type, parameterAnnotations, methodAnnotations, retrofit); } } Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .addConverterFactory(new MyConverterFactory()) .build(); Annotated annotated = retrofit.create(Annotated.class); annotated.bodyParameter(null); // Trigger internal setup. assertThat(parameterAnnotationsRef.get()).hasAtLeastOneElementOfType(Annotated.Foo.class); assertThat(methodAnnotationsRef.get()).hasAtLeastOneElementOfType(POST.class); } @Test public void parameterAnnotationsPassedToStringConverter() { final AtomicReference<Annotation[]> annotationsRef = new AtomicReference<>(); class MyConverterFactory extends Converter.Factory { @Override public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) { annotationsRef.set(annotations); return new Converter<Object, String>() { @Override public String convert(Object value) throws IOException { return String.valueOf(value); } }; } } Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .addConverterFactory(new MyConverterFactory()) .build(); Annotated annotated = retrofit.create(Annotated.class); annotated.queryParameter(null); // Trigger internal setup. Annotation[] annotations = annotationsRef.get(); assertThat(annotations).hasAtLeastOneElementOfType(Annotated.Foo.class); } @Test public void stringConverterCalledForString() { final AtomicBoolean factoryCalled = new AtomicBoolean(); class MyConverterFactory extends Converter.Factory { @Override public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) { factoryCalled.set(true); return null; } } Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .addConverterFactory(new MyConverterFactory()) .build(); CallMethod service = retrofit.create(CallMethod.class); Call<ResponseBody> call = service.queryString(null); assertThat(call).isNotNull(); assertThat(factoryCalled.get()).isTrue(); } @Test public void stringConverterReturningNullResultsInDefault() { final AtomicBoolean factoryCalled = new AtomicBoolean(); class MyConverterFactory extends Converter.Factory { @Override public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) { factoryCalled.set(true); return null; } } Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .addConverterFactory(new MyConverterFactory()) .build(); CallMethod service = retrofit.create(CallMethod.class); Call<ResponseBody> call = service.queryObject(null); assertThat(call).isNotNull(); assertThat(factoryCalled.get()).isTrue(); } @Test public void missingConverterThrowsOnNonRequestBody() throws IOException { Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .build(); CallMethod example = retrofit.create(CallMethod.class); try { example.disallowed("Hi!"); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("" + "Unable to create @Body converter for class java.lang.String (parameter #1)\n" + " for method CallMethod.disallowed"); assertThat(e.getCause()).hasMessage("" + "Could not locate RequestBody converter for class java.lang.String.\n" + " Tried:\n" + " * retrofit2.BuiltInConverters"); } } @Test public void missingConverterThrowsOnNonResponseBody() throws IOException { Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .build(); CallMethod example = retrofit.create(CallMethod.class); server.enqueue(new MockResponse().setBody("Hi")); try { example.disallowed(); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("" + "Unable to create converter for class java.lang.String\n" + " for method CallMethod.disallowed"); assertThat(e.getCause()).hasMessage("" + "Could not locate ResponseBody converter for class java.lang.String.\n" + " Tried:\n" + " * retrofit2.BuiltInConverters"); } } @Test public void requestBodyOutgoingAllowed() throws IOException { Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .build(); CallMethod example = retrofit.create(CallMethod.class); server.enqueue(new MockResponse().setBody("Hi")); Response<ResponseBody> response = example.getResponseBody().execute(); assertThat(response.body().string()).isEqualTo("Hi"); } @Test public void voidOutgoingAllowed() throws IOException { Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .build(); CallMethod example = retrofit.create(CallMethod.class); server.enqueue(new MockResponse().setBody("Hi")); Response<Void> response = example.getVoid().execute(); assertThat(response.body()).isNull(); } @Test public void voidResponsesArePooled() throws Exception { Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .build(); CallMethod example = retrofit.create(CallMethod.class); server.enqueue(new MockResponse().setBody("abc")); server.enqueue(new MockResponse().setBody("def")); example.getVoid().execute(); example.getVoid().execute(); assertThat(server.takeRequest().getSequenceNumber()).isEqualTo(0); assertThat(server.takeRequest().getSequenceNumber()).isEqualTo(1); } @Test public void responseBodyIncomingAllowed() throws IOException, InterruptedException { Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .build(); CallMethod example = retrofit.create(CallMethod.class); server.enqueue(new MockResponse().setBody("Hi")); RequestBody body = RequestBody.create(MediaType.parse("text/plain"), "Hey"); Response<ResponseBody> response = example.postRequestBody(body).execute(); assertThat(response.body().string()).isEqualTo("Hi"); assertThat(server.takeRequest().getBody().readUtf8()).isEqualTo("Hey"); } @Test public void unresolvableResponseTypeThrows() { Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .addConverterFactory(new ToStringConverterFactory()) .build(); UnresolvableResponseType example = retrofit.create(UnresolvableResponseType.class); try { example.typeVariable(); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Method return type must not include a type variable or wildcard: " + "retrofit2.Call<T>\n for method UnresolvableResponseType.typeVariable"); } try { example.typeVariableUpperBound(); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Method return type must not include a type variable or wildcard: " + "retrofit2.Call<T>\n for method UnresolvableResponseType.typeVariableUpperBound"); } try { example.crazy(); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Method return type must not include a type variable or wildcard: " + "retrofit2.Call<java.util.List<java.util.Map<java.lang.String, java.util.Set<T[]>>>>\n" + " for method UnresolvableResponseType.crazy"); } try { example.wildcard(); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Method return type must not include a type variable or wildcard: " + "retrofit2.Call<?>\n for method UnresolvableResponseType.wildcard"); } try { example.wildcardUpperBound(); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Method return type must not include a type variable or wildcard: " + "retrofit2.Call<? extends okhttp3.ResponseBody>\n" + " for method UnresolvableResponseType.wildcardUpperBound"); } } @Test public void unresolvableParameterTypeThrows() { Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .addConverterFactory(new ToStringConverterFactory()) .build(); UnresolvableParameterType example = retrofit.create(UnresolvableParameterType.class); try { example.typeVariable(null); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Parameter type must not include a type variable or wildcard: " + "T (parameter #1)\n for method UnresolvableParameterType.typeVariable"); } try { example.typeVariableUpperBound(null); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Parameter type must not include a type variable or wildcard: " + "T (parameter #1)\n for method UnresolvableParameterType.typeVariableUpperBound"); } try { example.crazy(null); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Parameter type must not include a type variable or wildcard: " + "java.util.List<java.util.Map<java.lang.String, java.util.Set<T[]>>> (parameter #1)\n" + " for method UnresolvableParameterType.crazy"); } try { example.wildcard(null); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Parameter type must not include a type variable or wildcard: " + "java.util.List<?> (parameter #1)\n for method UnresolvableParameterType.wildcard"); } try { example.wildcardUpperBound(null); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Parameter type must not include a type variable or wildcard: " + "java.util.List<? extends okhttp3.RequestBody> (parameter #1)\n" + " for method UnresolvableParameterType.wildcardUpperBound"); } } @Test public void baseUrlRequired() { try { new Retrofit.Builder().build(); fail(); } catch (IllegalStateException e) { assertThat(e).hasMessage("Base URL required."); } } @Test public void baseUrlNullThrows() { try { new Retrofit.Builder().baseUrl((String) null); fail(); } catch (NullPointerException e) { assertThat(e).hasMessage("baseUrl == null"); } try { new Retrofit.Builder().baseUrl((HttpUrl) null); fail(); } catch (NullPointerException e) { assertThat(e).hasMessage("baseUrl == null"); } } @Test public void baseUrlInvalidThrows() { try { new Retrofit.Builder().baseUrl("ftp://foo/bar"); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Illegal URL: ftp://foo/bar"); } } @Test public void baseUrlNoTrailingSlashThrows() { try { new Retrofit.Builder().baseUrl("http://example.com/api"); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("baseUrl must end in /: http://example.com/api"); } HttpUrl parsed = HttpUrl.parse("http://example.com/api"); try { new Retrofit.Builder().baseUrl(parsed); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("baseUrl must end in /: http://example.com/api"); } } @Test public void baseUrlStringPropagated() { Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://example.com/") .build(); HttpUrl baseUrl = retrofit.baseUrl(); assertThat(baseUrl).isEqualTo(HttpUrl.parse("http://example.com/")); } @Test public void baseHttpUrlPropagated() { HttpUrl url = HttpUrl.parse("http://example.com/"); Retrofit retrofit = new Retrofit.Builder() .baseUrl(url) .build(); assertThat(retrofit.baseUrl()).isSameAs(url); } @Test public void clientNullThrows() { try { new Retrofit.Builder().client(null); fail(); } catch (NullPointerException e) { assertThat(e).hasMessage("client == null"); } } @Test public void callFactoryDefault() { Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://example.com") .build(); assertThat(retrofit.callFactory()).isNotNull(); } @Test public void callFactoryPropagated() { okhttp3.Call.Factory callFactory = mock(okhttp3.Call.Factory.class); Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://example.com/") .callFactory(callFactory) .build(); assertThat(retrofit.callFactory()).isSameAs(callFactory); } @Test public void callFactoryClientPropagated() { OkHttpClient client = new OkHttpClient(); Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://example.com/") .client(client) .build(); assertThat(retrofit.callFactory()).isSameAs(client); } @Test public void callFactoryUsed() throws IOException { okhttp3.Call.Factory callFactory = spy(new okhttp3.Call.Factory() { @Override public okhttp3.Call newCall(Request request) { return new OkHttpClient().newCall(request); } }); Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .callFactory(callFactory) .build(); server.enqueue(new MockResponse()); CallMethod service = retrofit.create(CallMethod.class); service.getResponseBody().execute(); verify(callFactory).newCall(any(Request.class)); verifyNoMoreInteractions(callFactory); } @Test public void callFactoryReturningNullThrows() throws IOException { okhttp3.Call.Factory callFactory = new okhttp3.Call.Factory() { @Override public okhttp3.Call newCall(Request request) { return null; } }; Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://example.com/") .callFactory(callFactory) .build(); server.enqueue(new MockResponse()); CallMethod service = retrofit.create(CallMethod.class); Call<ResponseBody> call = service.getResponseBody(); try { call.execute(); fail(); } catch (NullPointerException e) { assertThat(e).hasMessage("Call.Factory returned null."); } } @Test public void callFactoryThrowingPropagates() { final RuntimeException cause = new RuntimeException("Broken!"); okhttp3.Call.Factory callFactory = new okhttp3.Call.Factory() { @Override public okhttp3.Call newCall(Request request) { throw cause; } }; Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://example.com/") .callFactory(callFactory) .build(); server.enqueue(new MockResponse()); CallMethod service = retrofit.create(CallMethod.class); Call<ResponseBody> call = service.getResponseBody(); try { call.execute(); fail(); } catch (Exception e) { assertThat(e).isSameAs(cause); } } @Test public void converterNullThrows() { try { new Retrofit.Builder().addConverterFactory(null); fail(); } catch (NullPointerException e) { assertThat(e).hasMessage("factory == null"); } } @Test public void converterFactoryDefault() { Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://example.com/") .build(); List<Converter.Factory> converterFactories = retrofit.converterFactories(); assertThat(converterFactories).hasSize(1); assertThat(converterFactories.get(0)).isInstanceOf(BuiltInConverters.class); } @Test public void requestConverterFactoryQueried() { Type type = String.class; Annotation[] parameterAnnotations = new Annotation[0]; Annotation[] methodAnnotations = new Annotation[1]; Converter<?, RequestBody> expectedAdapter = mock(Converter.class); Converter.Factory factory = mock(Converter.Factory.class); Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://example.com/") .addConverterFactory(factory) .build(); doReturn(expectedAdapter).when(factory).requestBodyConverter(type, parameterAnnotations, methodAnnotations, retrofit); Converter<?, RequestBody> actualAdapter = retrofit.requestBodyConverter(type, parameterAnnotations, methodAnnotations); assertThat(actualAdapter).isSameAs(expectedAdapter); verify(factory).requestBodyConverter(type, parameterAnnotations, methodAnnotations, retrofit); verifyNoMoreInteractions(factory); } @Test public void requestConverterFactoryNoMatchThrows() { Type type = String.class; Annotation[] annotations = new Annotation[0]; NonMatchingConverterFactory nonMatchingFactory = new NonMatchingConverterFactory(); Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://example.com/") .addConverterFactory(nonMatchingFactory) .build(); try { retrofit.requestBodyConverter(type, annotations, annotations); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("" + "Could not locate RequestBody converter for class java.lang.String.\n" + " Tried:\n" + " * retrofit2.BuiltInConverters\n" + " * retrofit2.helpers.NonMatchingConverterFactory"); } assertThat(nonMatchingFactory.called).isTrue(); } @Test public void requestConverterFactorySkippedNoMatchThrows() { Type type = String.class; Annotation[] annotations = new Annotation[0]; NonMatchingConverterFactory nonMatchingFactory1 = new NonMatchingConverterFactory(); NonMatchingConverterFactory nonMatchingFactory2 = new NonMatchingConverterFactory(); Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://example.com/") .addConverterFactory(nonMatchingFactory1) .addConverterFactory(nonMatchingFactory2) .build(); try { retrofit.nextRequestBodyConverter(nonMatchingFactory1, type, annotations, annotations); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("" + "Could not locate RequestBody converter for class java.lang.String.\n" + " Skipped:\n" + " * retrofit2.BuiltInConverters\n" + " * retrofit2.helpers.NonMatchingConverterFactory\n" + " Tried:\n" + " * retrofit2.helpers.NonMatchingConverterFactory"); } assertThat(nonMatchingFactory1.called).isFalse(); assertThat(nonMatchingFactory2.called).isTrue(); } @Test public void responseConverterFactoryQueried() { Type type = String.class; Annotation[] annotations = new Annotation[0]; Converter<ResponseBody, ?> expectedAdapter = mock(Converter.class); Converter.Factory factory = mock(Converter.Factory.class); Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://example.com/") .addConverterFactory(factory) .build(); doReturn(expectedAdapter).when(factory).responseBodyConverter(type, annotations, retrofit); Converter<ResponseBody, ?> actualAdapter = retrofit.responseBodyConverter(type, annotations); assertThat(actualAdapter).isSameAs(expectedAdapter); verify(factory).responseBodyConverter(type, annotations, retrofit); verifyNoMoreInteractions(factory); } @Test public void responseConverterFactoryNoMatchThrows() { Type type = String.class; Annotation[] annotations = new Annotation[0]; NonMatchingConverterFactory nonMatchingFactory = new NonMatchingConverterFactory(); Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://example.com/") .addConverterFactory(nonMatchingFactory) .build(); try { retrofit.responseBodyConverter(type, annotations); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("" + "Could not locate ResponseBody converter for class java.lang.String.\n" + " Tried:\n" + " * retrofit2.BuiltInConverters\n" + " * retrofit2.helpers.NonMatchingConverterFactory"); } assertThat(nonMatchingFactory.called).isTrue(); } @Test public void responseConverterFactorySkippedNoMatchThrows() { Type type = String.class; Annotation[] annotations = new Annotation[0]; NonMatchingConverterFactory nonMatchingFactory1 = new NonMatchingConverterFactory(); NonMatchingConverterFactory nonMatchingFactory2 = new NonMatchingConverterFactory(); Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://example.com/") .addConverterFactory(nonMatchingFactory1) .addConverterFactory(nonMatchingFactory2) .build(); try { retrofit.nextResponseBodyConverter(nonMatchingFactory1, type, annotations); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("" + "Could not locate ResponseBody converter for class java.lang.String.\n" + " Skipped:\n" + " * retrofit2.BuiltInConverters\n" + " * retrofit2.helpers.NonMatchingConverterFactory\n" + " Tried:\n" + " * retrofit2.helpers.NonMatchingConverterFactory"); } assertThat(nonMatchingFactory1.called).isFalse(); assertThat(nonMatchingFactory2.called).isTrue(); } @Test public void stringConverterFactoryQueried() { Type type = Object.class; Annotation[] annotations = new Annotation[0]; Converter<?, String> expectedAdapter = mock(Converter.class); Converter.Factory factory = mock(Converter.Factory.class); Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://example.com/") .addConverterFactory(factory) .build(); doReturn(expectedAdapter).when(factory).stringConverter(type, annotations, retrofit); Converter<?, String> actualAdapter = retrofit.stringConverter(type, annotations); assertThat(actualAdapter).isSameAs(expectedAdapter); verify(factory).stringConverter(type, annotations, retrofit); verifyNoMoreInteractions(factory); } @Test public void converterFactoryPropagated() { Converter.Factory factory = mock(Converter.Factory.class); Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://example.com/") .addConverterFactory(factory) .build(); assertThat(retrofit.converterFactories()).contains(factory); } @Test public void callAdapterFactoryNullThrows() { try { new Retrofit.Builder().addCallAdapterFactory(null); fail(); } catch (NullPointerException e) { assertThat(e).hasMessage("factory == null"); } } @Test public void callAdapterFactoryDefault() { Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://example.com/") .build(); assertThat(retrofit.callAdapterFactories()).isNotEmpty(); } @Test public void callAdapterFactoryPropagated() { CallAdapter.Factory factory = mock(CallAdapter.Factory.class); Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://example.com/") .addCallAdapterFactory(factory) .build(); assertThat(retrofit.callAdapterFactories()).contains(factory); } @Test public void callAdapterFactoryQueried() { Type type = String.class; Annotation[] annotations = new Annotation[0]; CallAdapter<?, ?> expectedAdapter = mock(CallAdapter.class); CallAdapter.Factory factory = mock(CallAdapter.Factory.class); Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://example.com/") .addCallAdapterFactory(factory) .build(); doReturn(expectedAdapter).when(factory).get(type, annotations, retrofit); CallAdapter<?, ?> actualAdapter = retrofit.callAdapter(type, annotations); assertThat(actualAdapter).isSameAs(expectedAdapter); verify(factory).get(type, annotations, retrofit); verifyNoMoreInteractions(factory); } @Test public void callAdapterFactoryQueriedCanDelegate() { Type type = String.class; Annotation[] annotations = new Annotation[0]; CallAdapter<?, ?> expectedAdapter = mock(CallAdapter.class); CallAdapter.Factory factory2 = mock(CallAdapter.Factory.class); CallAdapter.Factory factory1 = spy(new CallAdapter.Factory() { @Override public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) { return retrofit.nextCallAdapter(this, returnType, annotations); } }); Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://example.com/") .addCallAdapterFactory(factory1) .addCallAdapterFactory(factory2) .build(); doReturn(expectedAdapter).when(factory2).get(type, annotations, retrofit); CallAdapter<?, ?> actualAdapter = retrofit.callAdapter(type, annotations); assertThat(actualAdapter).isSameAs(expectedAdapter); verify(factory1).get(type, annotations, retrofit); verifyNoMoreInteractions(factory1); verify(factory2).get(type, annotations, retrofit); verifyNoMoreInteractions(factory2); } @Test public void callAdapterFactoryQueriedCanDelegateTwiceWithoutRecursion() { Type type = String.class; Annotation[] annotations = new Annotation[0]; CallAdapter<?, ?> expectedAdapter = mock(CallAdapter.class); CallAdapter.Factory factory3 = mock(CallAdapter.Factory.class); CallAdapter.Factory factory2 = spy(new CallAdapter.Factory() { @Override public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) { return retrofit.nextCallAdapter(this, returnType, annotations); } }); CallAdapter.Factory factory1 = spy(new CallAdapter.Factory() { @Override public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) { return retrofit.nextCallAdapter(this, returnType, annotations); } }); Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://example.com/") .addCallAdapterFactory(factory1) .addCallAdapterFactory(factory2) .addCallAdapterFactory(factory3) .build(); doReturn(expectedAdapter).when(factory3).get(type, annotations, retrofit); CallAdapter<?, ?> actualAdapter = retrofit.callAdapter(type, annotations); assertThat(actualAdapter).isSameAs(expectedAdapter); verify(factory1).get(type, annotations, retrofit); verifyNoMoreInteractions(factory1); verify(factory2).get(type, annotations, retrofit); verifyNoMoreInteractions(factory2); verify(factory3).get(type, annotations, retrofit); verifyNoMoreInteractions(factory3); } @Test public void callAdapterFactoryNoMatchThrows() { Type type = String.class; Annotation[] annotations = new Annotation[0]; NonMatchingCallAdapterFactory nonMatchingFactory = new NonMatchingCallAdapterFactory(); Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://example.com/") .addCallAdapterFactory(nonMatchingFactory) .build(); try { retrofit.callAdapter(type, annotations); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("" + "Could not locate call adapter for class java.lang.String.\n" + " Tried:\n" + " * retrofit2.helpers.NonMatchingCallAdapterFactory\n" + " * retrofit2.DefaultCallAdapterFactory"); } assertThat(nonMatchingFactory.called).isTrue(); } @Test public void callAdapterFactoryDelegateNoMatchThrows() { Type type = String.class; Annotation[] annotations = new Annotation[0]; DelegatingCallAdapterFactory delegatingFactory1 = new DelegatingCallAdapterFactory(); DelegatingCallAdapterFactory delegatingFactory2 = new DelegatingCallAdapterFactory(); NonMatchingCallAdapterFactory nonMatchingFactory = new NonMatchingCallAdapterFactory(); Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://example.com/") .addCallAdapterFactory(delegatingFactory1) .addCallAdapterFactory(delegatingFactory2) .addCallAdapterFactory(nonMatchingFactory) .build(); try { retrofit.callAdapter(type, annotations); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("" + "Could not locate call adapter for class java.lang.String.\n" + " Skipped:\n" + " * retrofit2.helpers.DelegatingCallAdapterFactory\n" + " * retrofit2.helpers.DelegatingCallAdapterFactory\n" + " Tried:\n" + " * retrofit2.helpers.NonMatchingCallAdapterFactory\n" + " * retrofit2.DefaultCallAdapterFactory"); } assertThat(delegatingFactory1.called).isTrue(); assertThat(delegatingFactory2.called).isTrue(); assertThat(nonMatchingFactory.called).isTrue(); } @Test public void callbackExecutorNullThrows() { try { new Retrofit.Builder().callbackExecutor(null); fail(); } catch (NullPointerException e) { assertThat(e).hasMessage("executor == null"); } } @Test public void callbackExecutorPropagatesDefaultJvm() { Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://example.com/") .build(); assertThat(retrofit.callbackExecutor()).isNull(); } @Test public void callbackExecutorPropagatesDefaultAndroid() { final Executor executor = Executors.newSingleThreadExecutor(); Platform platform = new Platform() { @Override Executor defaultCallbackExecutor() { return executor; } }; Retrofit retrofit = new Retrofit.Builder(platform) .baseUrl("http://example.com/") .build(); assertThat(retrofit.callbackExecutor()).isSameAs(executor); } @Test public void callbackExecutorPropagated() { Executor executor = mock(Executor.class); Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://example.com/") .callbackExecutor(executor) .build(); assertThat(retrofit.callbackExecutor()).isSameAs(executor); } @Test public void callbackExecutorUsedForSuccess() throws InterruptedException { Executor executor = spy(new Executor() { @Override public void execute(Runnable command) { command.run(); } }); Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .callbackExecutor(executor) .build(); CallMethod service = retrofit.create(CallMethod.class); Call<ResponseBody> call = service.getResponseBody(); server.enqueue(new MockResponse()); final CountDownLatch latch = new CountDownLatch(1); call.enqueue(new Callback<ResponseBody>() { @Override public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) { latch.countDown(); } @Override public void onFailure(Call<ResponseBody> call, Throwable t) { t.printStackTrace(); } }); assertTrue(latch.await(2, TimeUnit.SECONDS)); verify(executor).execute(any(Runnable.class)); verifyNoMoreInteractions(executor); } @Test public void callbackExecutorUsedForFailure() throws InterruptedException { Executor executor = spy(new Executor() { @Override public void execute(Runnable command) { command.run(); } }); Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .callbackExecutor(executor) .build(); CallMethod service = retrofit.create(CallMethod.class); Call<ResponseBody> call = service.getResponseBody(); server.enqueue(new MockResponse().setSocketPolicy(DISCONNECT_AT_START)); final CountDownLatch latch = new CountDownLatch(1); call.enqueue(new Callback<ResponseBody>() { @Override public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) { throw new AssertionError(); } @Override public void onFailure(Call<ResponseBody> call, Throwable t) { latch.countDown(); } }); assertTrue(latch.await(2, TimeUnit.SECONDS)); verify(executor).execute(any(Runnable.class)); verifyNoMoreInteractions(executor); } /** Confirm that Retrofit encodes parameters when the call is executed, and not earlier. */ @Test public void argumentCapture() throws Exception { AtomicInteger i = new AtomicInteger(); server.enqueue(new MockResponse().setBody("a")); server.enqueue(new MockResponse().setBody("b")); Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) .addConverterFactory(new ToStringConverterFactory()) .build(); MutableParameters mutableParameters = retrofit.create(MutableParameters.class); i.set(100); Call<String> call1 = mutableParameters.method(i); i.set(101); Response<String> response1 = call1.execute(); i.set(102); assertEquals("a", response1.body()); assertEquals("/?i=101", server.takeRequest().getPath()); i.set(200); Call<String> call2 = call1.clone(); i.set(201); Response<String> response2 = call2.execute(); i.set(202); assertEquals("b", response2.body()); assertEquals("/?i=201", server.takeRequest().getPath()); } }