// Copyright 2013 Square, Inc.
package retrofit;
import org.junit.Before;
import org.junit.Test;
import retrofit.client.Client;
import retrofit.client.Request;
import retrofit.client.Response;
import rx.Observable;
import rx.util.functions.Action1;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import java.io.IOException;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
import static retrofit.MockRestAdapter.ValueChangeListener;
import static retrofit.Utils.SynchronousExecutor;
public class MockRestAdapterTest
{
interface SyncExample
{
@GET
@Path("/")
Object doStuff();
}
interface AsyncExample
{
@GET
@Path("/")
void doStuff(Callback<Object> cb);
}
interface ObservableExample
{
@GET
@Path("/")
Observable<Object> doStuff();
}
private Executor httpExecutor;
private Executor callbackExecutor;
private MockRestAdapter mockRestAdapter;
private ValueChangeListener valueChangeListener;
@Before
public void setUp() throws IOException
{
Client client = mock(Client.class);
doThrow(new AssertionError()).when(client).execute(any(Request.class));
httpExecutor = spy(new SynchronousExecutor());
callbackExecutor = spy(new SynchronousExecutor());
RestAdapter restAdapter = new RestAdapter.Builder() //
.setClient(client)
.setExecutors(httpExecutor, callbackExecutor)
.setServer("http://example.com")
.setLogLevel(RestAdapter.LogLevel.NONE)
.build();
valueChangeListener = mock(ValueChangeListener.class);
mockRestAdapter = MockRestAdapter.from(restAdapter);
mockRestAdapter.setValueChangeListener(valueChangeListener);
// Seed the random with a value so the tests are deterministic.
mockRestAdapter.random.setSeed(2847);
}
@Test
public void delayRestrictsRange()
{
try
{
mockRestAdapter.setDelay(-1);
fail();
}
catch (IllegalArgumentException e)
{
assertThat(e).hasMessage("Delay must be positive value.");
}
try
{
mockRestAdapter.setDelay(Long.MAX_VALUE);
fail();
}
catch (IllegalArgumentException e)
{
assertThat(e).hasMessageStartingWith("Delay value too large.");
}
}
@Test
public void varianceRestrictsRange()
{
try
{
mockRestAdapter.setVariancePercentage(-13);
fail();
}
catch (IllegalArgumentException e)
{
assertThat(e).hasMessage("Variance percentage must be between 0 and 100.");
}
try
{
mockRestAdapter.setVariancePercentage(174);
fail();
}
catch (IllegalArgumentException e)
{
assertThat(e).hasMessage("Variance percentage must be between 0 and 100.");
}
}
@Test
public void errorRestrictsRange()
{
try
{
mockRestAdapter.setErrorPercentage(-13);
fail();
}
catch (IllegalArgumentException e)
{
assertThat(e).hasMessage("Error percentage must be between 0 and 100.");
}
try
{
mockRestAdapter.setErrorPercentage(174);
fail();
}
catch (IllegalArgumentException e)
{
assertThat(e).hasMessage("Error percentage must be between 0 and 100.");
}
}
@Test
public void errorPercentageIsAccurate()
{
mockRestAdapter.setErrorPercentage(0);
for (int i = 0; i < 10000; i++)
{
assertThat(mockRestAdapter.calculateIsFailure()).isFalse();
}
mockRestAdapter.setErrorPercentage(3);
int failures = 0;
for (int i = 0; i < 100000; i++)
{
if (mockRestAdapter.calculateIsFailure())
{
failures += 1;
}
}
assertThat(failures).isEqualTo(2964); // ~3% of 100k
}
@Test
public void delayVarianceIsAccurate()
{
mockRestAdapter.setDelay(2000);
mockRestAdapter.setVariancePercentage(0);
for (int i = 0; i < 100000; i++)
{
assertThat(mockRestAdapter.calculateDelayForCall()).isEqualTo(2000);
}
mockRestAdapter.setVariancePercentage(40);
int lowerBound = Integer.MAX_VALUE;
int upperBound = Integer.MIN_VALUE;
for (int i = 0; i < 100000; i++)
{
int delay = mockRestAdapter.calculateDelayForCall();
if (delay > upperBound)
{
upperBound = delay;
}
if (delay < lowerBound)
{
lowerBound = delay;
}
}
assertThat(upperBound).isEqualTo(2799); // ~40% above 2000
assertThat(lowerBound).isEqualTo(1200); // ~40% below 2000
}
@Test
public void errorVarianceIsAccurate()
{
mockRestAdapter.setDelay(2000);
int lowerBound = Integer.MAX_VALUE;
int upperBound = Integer.MIN_VALUE;
for (int i = 0; i < 100000; i++)
{
int delay = mockRestAdapter.calculateDelayForError();
if (delay > upperBound)
{
upperBound = delay;
}
if (delay < lowerBound)
{
lowerBound = delay;
}
}
assertThat(upperBound).isEqualTo(5999); // 3 * 2000
assertThat(lowerBound).isEqualTo(0);
}
@Test
public void changeListenerOnlyInvokedWhenValueHasChanged()
{
long delay = mockRestAdapter.getDelay();
int variance = mockRestAdapter.getVariancePercentage();
int error = mockRestAdapter.getErrorPercentage();
long newDelay = delay + 1;
mockRestAdapter.setDelay(newDelay);
verify(valueChangeListener).onMockValuesChanged(newDelay, variance, error);
int newError = error + 1;
mockRestAdapter.setErrorPercentage(newError);
verify(valueChangeListener).onMockValuesChanged(newDelay, variance, newError);
int newVariance = variance + 1;
mockRestAdapter.setVariancePercentage(newVariance);
verify(valueChangeListener).onMockValuesChanged(newDelay, newVariance, newError);
// Now try setting the same values and ensure the listener was never called.
mockRestAdapter.setDelay(newDelay);
mockRestAdapter.setVariancePercentage(newVariance);
mockRestAdapter.setErrorPercentage(newError);
verifyNoMoreInteractions(valueChangeListener);
}
@Test
public void syncFailureTriggersNetworkError()
{
mockRestAdapter.setErrorPercentage(100);
mockRestAdapter.setDelay(1);
class MockSyncExample implements SyncExample
{
@Override
public Object doStuff()
{
throw new AssertionError();
}
}
SyncExample mockService = mockRestAdapter.create(SyncExample.class, new MockSyncExample());
try
{
mockService.doStuff();
fail();
}
catch (RetrofitError e)
{
assertThat(e.isNetworkError()).isTrue();
assertThat(e.getCause()).hasMessage("Mock network error!");
}
}
@Test
public void asyncFailureTriggersNetworkError()
{
mockRestAdapter.setDelay(1);
mockRestAdapter.setErrorPercentage(100);
class MockAsyncExample implements AsyncExample
{
@Override
public void doStuff(Callback<Object> cb)
{
throw new AssertionError();
}
}
AsyncExample mockService = mockRestAdapter.create(AsyncExample.class, new MockAsyncExample());
final AtomicReference<RetrofitError> errorRef = new AtomicReference<RetrofitError>();
mockService.doStuff(new Callback<Object>()
{
@Override
public void success(Object o, Response response)
{
throw new AssertionError();
}
@Override
public void failure(RetrofitError error)
{
errorRef.set(error);
}
});
verify(httpExecutor).execute(any(Runnable.class));
verify(callbackExecutor).execute(any(Runnable.class));
RetrofitError error = errorRef.get();
assertThat(error.isNetworkError()).isTrue();
assertThat(error.getCause()).hasMessage("Mock network error!");
}
@Test
public void syncApiIsCalledWithDelay()
{
mockRestAdapter.setDelay(100);
mockRestAdapter.setVariancePercentage(0);
mockRestAdapter.setErrorPercentage(0);
final AtomicBoolean called = new AtomicBoolean();
final Object expected = new Object();
class MockSyncExample implements SyncExample
{
@Override
public Object doStuff()
{
called.set(true);
return expected;
}
}
SyncExample mockService = mockRestAdapter.create(SyncExample.class, new MockSyncExample());
long startNanos = System.nanoTime();
Object actual = mockService.doStuff();
long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos);
assertThat(called.get()).isTrue();
assertThat(actual).isEqualTo(expected);
assertThat(tookMs).isGreaterThanOrEqualTo(100);
}
@Test
public void asyncApiIsCalledWithDelay()
{
mockRestAdapter.setDelay(100);
mockRestAdapter.setVariancePercentage(0);
mockRestAdapter.setErrorPercentage(0);
final Object expected = new Object();
class MockAsyncExample implements AsyncExample
{
@Override
public void doStuff(Callback<Object> cb)
{
cb.success(expected, null);
}
}
AsyncExample mockService = mockRestAdapter.create(AsyncExample.class, new MockAsyncExample());
final long startNanos = System.nanoTime();
final AtomicLong tookMs = new AtomicLong();
final AtomicReference<Object> actual = new AtomicReference<Object>();
mockService.doStuff(new Callback<Object>()
{
@Override
public void success(Object result, Response response)
{
tookMs.set(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos));
actual.set(result);
}
@Override
public void failure(RetrofitError error)
{
throw new AssertionError();
}
});
verify(httpExecutor).execute(any(Runnable.class));
verify(callbackExecutor).execute(any(Runnable.class));
assertThat(actual.get()).isNotNull().isSameAs(expected);
assertThat(tookMs.get()).isGreaterThanOrEqualTo(100);
}
@Test
public void observableApiIsCalledWithDelay()
{
mockRestAdapter.setDelay(100);
mockRestAdapter.setVariancePercentage(0);
mockRestAdapter.setErrorPercentage(0);
final Object expected = new Object();
class MockObservableExample implements ObservableExample
{
@Override
public Observable<Object> doStuff()
{
return Observable.from(expected);
}
}
ObservableExample mockService =
mockRestAdapter.create(ObservableExample.class, new MockObservableExample());
final long startNanos = System.nanoTime();
final AtomicLong tookMs = new AtomicLong();
final AtomicReference<Object> actual = new AtomicReference<Object>();
Action1<Object> onSuccess = new Action1<Object>()
{
@Override
public void call(Object o)
{
tookMs.set(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos));
actual.set(o);
}
};
Action1<Throwable> onError = new Action1<Throwable>()
{
@Override
public void call(Throwable throwable)
{
throw new AssertionError();
}
};
mockService.doStuff().subscribe(onSuccess, onError);
verify(httpExecutor, atLeastOnce()).execute(any(Runnable.class));
verifyZeroInteractions(callbackExecutor);
assertThat(actual.get()).isNotNull().isSameAs(expected);
assertThat(tookMs.get()).isGreaterThanOrEqualTo(100);
}
@Test
public void syncHttpExceptionBecomesError()
{
mockRestAdapter.setDelay(100);
mockRestAdapter.setVariancePercentage(0);
mockRestAdapter.setErrorPercentage(0);
final Object expected = new Object();
class MockSyncExample implements SyncExample
{
@Override
public Object doStuff()
{
throw new MockHttpException(404, "Not Found", expected);
}
}
SyncExample mockService = mockRestAdapter.create(SyncExample.class, new MockSyncExample());
long startNanos = System.nanoTime();
try
{
mockService.doStuff();
fail();
}
catch (RetrofitError e)
{
long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos);
assertThat(tookMs).isGreaterThanOrEqualTo(100);
assertThat(e.isNetworkError()).isFalse();
assertThat(e.getResponse().getStatus()).isEqualTo(404);
assertThat(e.getResponse().getReason()).isEqualTo("Not Found");
assertThat(e.getBody()).isSameAs(expected);
}
}
@Test
public void asyncHttpExceptionBecomesError()
{
mockRestAdapter.setDelay(100);
mockRestAdapter.setVariancePercentage(0);
mockRestAdapter.setErrorPercentage(0);
final Object expected = new Object();
class MockAsyncExample implements AsyncExample
{
@Override
public void doStuff(Callback<Object> cb)
{
throw new MockHttpException(404, "Not Found", expected);
}
}
AsyncExample mockService = mockRestAdapter.create(AsyncExample.class, new MockAsyncExample());
final long startNanos = System.nanoTime();
final AtomicLong tookMs = new AtomicLong();
final AtomicReference<RetrofitError> errorRef = new AtomicReference<RetrofitError>();
mockService.doStuff(new Callback<Object>()
{
@Override
public void success(Object o, Response response)
{
throw new AssertionError();
}
@Override
public void failure(RetrofitError error)
{
tookMs.set(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos));
errorRef.set(error);
}
});
verify(httpExecutor).execute(any(Runnable.class));
verify(callbackExecutor).execute(any(Runnable.class));
RetrofitError error = errorRef.get();
assertThat(tookMs.get()).isGreaterThanOrEqualTo(100);
assertThat(error.isNetworkError()).isFalse();
assertThat(error.getResponse().getStatus()).isEqualTo(404);
assertThat(error.getResponse().getReason()).isEqualTo("Not Found");
assertThat(error.getBody()).isSameAs(expected);
}
@Test
public void observableHttpExceptionBecomesError()
{
mockRestAdapter.setDelay(100);
mockRestAdapter.setVariancePercentage(0);
mockRestAdapter.setErrorPercentage(0);
final Object expected = new Object();
class MockObservableExample implements ObservableExample
{
@Override
public Observable<Object> doStuff()
{
throw new MockHttpException(404, "Not Found", expected);
}
}
ObservableExample mockService =
mockRestAdapter.create(ObservableExample.class, new MockObservableExample());
final long startNanos = System.nanoTime();
final AtomicLong tookMs = new AtomicLong();
final AtomicReference<RetrofitError> errorRef = new AtomicReference<RetrofitError>();
mockService.doStuff().subscribe(new Action1<Object>()
{
@Override
public void call(Object o)
{
throw new AssertionError();
}
}, new Action1<Throwable>()
{
@Override
public void call(Throwable error)
{
assertThat(error).isInstanceOf(RetrofitError.class);
tookMs.set(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos));
errorRef.set((RetrofitError) error);
}
}
);
verify(httpExecutor, atLeastOnce()).execute(any(Runnable.class));
verifyZeroInteractions(callbackExecutor);
RetrofitError error = errorRef.get();
assertThat(tookMs.get()).isGreaterThanOrEqualTo(100);
assertThat(error.isNetworkError()).isFalse();
assertThat(error.getResponse().getStatus()).isEqualTo(404);
assertThat(error.getResponse().getReason()).isEqualTo("Not Found");
assertThat(error.getBody()).isSameAs(expected);
}
@Test
public void asyncCallToFailureIsNotAllowed()
{
mockRestAdapter.setErrorPercentage(0);
class MockAsyncExample implements AsyncExample
{
@Override
public void doStuff(Callback<Object> cb)
{
cb.failure(null);
}
}
AsyncExample mockService = mockRestAdapter.create(AsyncExample.class, new MockAsyncExample());
try
{
mockService.doStuff(mock(Callback.class));
fail();
}
catch (IllegalStateException e)
{
assertThat(e).hasMessage(
"Calling failure directly is not supported. Throw MockHttpException instead.");
}
}
}