package com.nurkiewicz.asyncretry;
import org.assertj.core.api.Assertions;
import org.mockito.InOrder;
import org.testng.annotations.Test;
import java.math.BigDecimal;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import static com.nurkiewicz.asyncretry.backoff.FixedIntervalBackoff.DEFAULT_PERIOD_MILLIS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* @author Tomasz Nurkiewicz
* @since 7/16/13, 10:51 PM
*/
public class AsyncRetryExecutorManualAbortTest extends AbstractBaseTestCase {
@Test
public void shouldRethrowIfFirstExecutionThrowsAnExceptionAndNoRetry() throws Exception {
//given
final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock).dontRetry();
given(serviceMock.sometimesFails()).
willThrow(new IllegalStateException(DON_T_PANIC));
//when
final CompletableFuture<String> future = executor.getWithRetry(serviceMock::sometimesFails);
//then
assertThat(future.isCompletedExceptionally()).isTrue();
try {
future.get();
Assertions.failBecauseExceptionWasNotThrown(IllegalStateException.class);
} catch (ExecutionException e) {
final Throwable actualCause = e.getCause();
assertThat(actualCause).isInstanceOf(IllegalStateException.class);
assertThat(actualCause.getMessage()).isEqualToIgnoringCase(DON_T_PANIC);
}
}
@Test
public void shouldRetryAfterOneExceptionAndReturnValue() throws Exception {
//given
final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);
given(serviceMock.sometimesFails()).
willThrow(IllegalStateException.class).
willReturn("Foo");
//when
final CompletableFuture<String> future = executor.getWithRetry(serviceMock::sometimesFails);
//then
assertThat(future.get()).isEqualTo("Foo");
}
@Test
public void shouldSucceedWhenOnlyOneRetryAllowed() throws Exception {
//given
final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock).withMaxRetries(1);
given(serviceMock.sometimesFails()).
willThrow(IllegalStateException.class).
willReturn("Foo");
//when
final CompletableFuture<String> future = executor.getWithRetry(serviceMock::sometimesFails);
//then
assertThat(future.get()).isEqualTo("Foo");
}
@Test
public void shouldRetryOnceIfFirstExecutionThrowsException() throws Exception {
//given
final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);
given(serviceMock.sometimesFails()).
willThrow(IllegalStateException.class).
willReturn("Foo");
//when
executor.getWithRetry(serviceMock::sometimesFails);
//then
verify(serviceMock, times(2)).sometimesFails();
}
@Test
public void shouldScheduleRetryWithDefaultDelay() throws Exception {
//given
final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);
given(serviceMock.sometimesFails()).
willThrow(IllegalStateException.class).
willReturn("Foo");
//when
executor.getWithRetry(serviceMock::sometimesFails);
//then
final InOrder inOrder = inOrder(schedulerMock);
inOrder.verify(schedulerMock).schedule(notNullRunnable(), eq(0L), millis());
inOrder.verify(schedulerMock).schedule(notNullRunnable(), eq(DEFAULT_PERIOD_MILLIS), millis());
inOrder.verifyNoMoreInteractions();
}
@Test
public void shouldPassCorrectRetryCountToEachInvocationInContext() throws Exception {
//given
final RetryExecutor executor = new AsyncRetryExecutor(schedulerMock);
given(serviceMock.calculateSum(0)).willThrow(IllegalStateException.class);
given(serviceMock.calculateSum(1)).willReturn(BigDecimal.ONE);
//when
executor.getWithRetry(ctx -> serviceMock.calculateSum(ctx.getRetryCount()));
//then
final InOrder order = inOrder(serviceMock);
order.verify(serviceMock).calculateSum(0);
order.verify(serviceMock).calculateSum(1);
order.verifyNoMoreInteractions();
}
}