/*
*
* Copyright 2016 Robert Winkler
*
* 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 io.github.resilience4j.retry.internal;
import io.github.resilience4j.retry.IntervalFunction;
import io.github.resilience4j.retry.Retry;
import io.github.resilience4j.retry.RetryConfig;
import io.github.resilience4j.test.HelloWorldService;
import io.vavr.API;
import io.vavr.CheckedFunction0;
import io.vavr.Predicates;
import io.vavr.control.Try;
import org.assertj.core.api.Assertions;
import org.junit.Before;
import org.junit.Test;
import org.mockito.BDDMockito;
import org.mockito.Mockito;
import javax.xml.ws.WebServiceException;
import java.util.concurrent.Callable;
import java.util.function.Supplier;
import static io.vavr.API.$;
public class SupplierRetryTest {
private HelloWorldService helloWorldService;
private long sleptTime = 0L;
@Before
public void setUp(){
helloWorldService = Mockito.mock(HelloWorldService.class);
RetryContext.sleepFunction = sleep -> sleptTime += sleep;
}
@Test
public void shouldNotRetry() {
// Given the HelloWorldService returns Hello world
BDDMockito.given(helloWorldService.returnHelloWorld()).willReturn("Hello world");
// Create a Retry with default configuration
Retry retryContext = Retry.ofDefaults("id");
// Decorate the invocation of the HelloWorldService
Supplier<String> supplier = Retry.decorateSupplier(retryContext, helloWorldService::returnHelloWorld);
// When
String result = supplier.get();
// Then the helloWorldService should be invoked 1 time
BDDMockito.then(helloWorldService).should(Mockito.times(1)).returnHelloWorld();
Assertions.assertThat(sleptTime).isEqualTo(0);
}
@Test
public void testDecorateSupplier() {
// Given the HelloWorldService throws an exception
BDDMockito.given(helloWorldService.returnHelloWorld()).willThrow(new WebServiceException("BAM!")).willReturn("Hello world");
// Create a Retry with default configuration
Retry retryContext = Retry.ofDefaults("id");
// Decorate the invocation of the HelloWorldService
Supplier<String> supplier = Retry.decorateSupplier(retryContext, helloWorldService::returnHelloWorld);
// When
String result = supplier.get();
// Then the helloWorldService should be invoked 2 times
BDDMockito.then(helloWorldService).should(Mockito.times(2)).returnHelloWorld();
Assertions.assertThat(result).isEqualTo("Hello world");
Assertions.assertThat(sleptTime).isEqualTo(RetryConfig.DEFAULT_WAIT_DURATION);
}
@Test
public void testDecorateCallable() throws Exception {
// Given the HelloWorldService throws an exception
BDDMockito.given(helloWorldService.returnHelloWorldWithException()).willThrow(new WebServiceException("BAM!")).willReturn("Hello world");
// Create a Retry with default configuration
Retry retryContext = Retry.ofDefaults("id");
// Decorate the invocation of the HelloWorldService
Callable<String> callable = Retry.decorateCallable(retryContext, helloWorldService::returnHelloWorldWithException);
// When
String result = callable.call();
// Then the helloWorldService should be invoked 2 times
BDDMockito.then(helloWorldService).should(Mockito.times(2)).returnHelloWorldWithException();
Assertions.assertThat(result).isEqualTo("Hello world");
Assertions.assertThat(sleptTime).isEqualTo(RetryConfig.DEFAULT_WAIT_DURATION);
}
@Test
public void testExecuteCallable() throws Exception {
// Given the HelloWorldService throws an exception
BDDMockito.given(helloWorldService.returnHelloWorldWithException()).willThrow(new WebServiceException("BAM!")).willReturn("Hello world");
// Create a Retry with default configuration
Retry retryContext = Retry.ofDefaults("id");
// Decorate the invocation of the HelloWorldService
String result = retryContext.executeCallable(helloWorldService::returnHelloWorldWithException);
// Then the helloWorldService should be invoked 2 times
BDDMockito.then(helloWorldService).should(Mockito.times(2)).returnHelloWorldWithException();
Assertions.assertThat(result).isEqualTo("Hello world");
Assertions.assertThat(sleptTime).isEqualTo(RetryConfig.DEFAULT_WAIT_DURATION);
}
@Test
public void testExecuteSupplier() {
// Given the HelloWorldService throws an exception
BDDMockito.given(helloWorldService.returnHelloWorld()).willThrow(new WebServiceException("BAM!")).willReturn("Hello world");
// Create a Retry with default configuration
Retry retryContext = Retry.ofDefaults("id");
// Decorate the invocation of the HelloWorldService
String result = retryContext.executeSupplier(helloWorldService::returnHelloWorld);
// Then the helloWorldService should be invoked 2 times
BDDMockito.then(helloWorldService).should(Mockito.times(2)).returnHelloWorld();
Assertions.assertThat(result).isEqualTo("Hello world");
Assertions.assertThat(sleptTime).isEqualTo(RetryConfig.DEFAULT_WAIT_DURATION);
}
@Test
public void shouldReturnSuccessfullyAfterSecondAttempt() {
// Given the HelloWorldService throws an exception
BDDMockito.given(helloWorldService.returnHelloWorld()).willThrow(new WebServiceException("BAM!")).willReturn("Hello world");
// Create a Retry with default configuration
Retry retryContext = Retry.ofDefaults("id");
// Decorate the invocation of the HelloWorldService
CheckedFunction0<String> retryableSupplier = Retry.decorateCheckedSupplier(retryContext, helloWorldService::returnHelloWorld);
// When
Try<String> result = Try.of(retryableSupplier);
// Then the helloWorldService should be invoked 2 times
BDDMockito.then(helloWorldService).should(Mockito.times(2)).returnHelloWorld();
Assertions.assertThat(result.get()).isEqualTo("Hello world");
Assertions.assertThat(sleptTime).isEqualTo(RetryConfig.DEFAULT_WAIT_DURATION);
}
@Test
public void shouldReturnAfterThreeAttempts() {
// Given the HelloWorldService throws an exception
BDDMockito.given(helloWorldService.returnHelloWorld()).willThrow(new WebServiceException("BAM!"));
// Create a Retry with default configuration
Retry retryContext = Retry.ofDefaults("id");
// Decorate the invocation of the HelloWorldService
CheckedFunction0<String> retryableSupplier = Retry.decorateCheckedSupplier(retryContext, helloWorldService::returnHelloWorld);
// When
Try<String> result = Try.of(retryableSupplier);
// Then the helloWorldService should be invoked 3 times
BDDMockito.then(helloWorldService).should(Mockito.times(3)).returnHelloWorld();
// and the result should be a failure
Assertions.assertThat(result.isFailure()).isTrue();
// and the returned exception should be of type RuntimeException
Assertions.assertThat(result.failed().get()).isInstanceOf(WebServiceException.class);
Assertions.assertThat(sleptTime).isEqualTo(RetryConfig.DEFAULT_WAIT_DURATION*2);
}
@Test
public void shouldReturnAfterOneAttempt() {
// Given the HelloWorldService throws an exception
BDDMockito.given(helloWorldService.returnHelloWorld()).willThrow(new WebServiceException("BAM!"));
// Create a Retry with custom configuration
RetryConfig config = RetryConfig.custom().maxAttempts(1).build();
Retry retryContext = Retry.of("id", config);
// Decorate the invocation of the HelloWorldService
CheckedFunction0<String> retryableSupplier = Retry.decorateCheckedSupplier(retryContext, helloWorldService::returnHelloWorld);
// When
Try<String> result = Try.of(retryableSupplier);
// Then the helloWorldService should be invoked 1 time
BDDMockito.then(helloWorldService).should(Mockito.times(1)).returnHelloWorld();
// and the result should be a failure
Assertions.assertThat(result.isFailure()).isTrue();
// and the returned exception should be of type RuntimeException
Assertions.assertThat(result.failed().get()).isInstanceOf(WebServiceException.class);
Assertions.assertThat(sleptTime).isEqualTo(0);
}
@Test
public void shouldReturnAfterOneAttemptAndIgnoreException() {
// Given the HelloWorldService throws an exception
BDDMockito.given(helloWorldService.returnHelloWorld()).willThrow(new WebServiceException("BAM!"));
// Create a Retry with default configuration
RetryConfig config = RetryConfig.custom()
.retryOnException(throwable -> API.Match(throwable).of(
API.Case($(Predicates.instanceOf(WebServiceException.class)), false),
API.Case($(), true)))
.build();
Retry retryContext = Retry.of("id", config);
// Decorate the invocation of the HelloWorldService
CheckedFunction0<String> retryableSupplier = Retry.decorateCheckedSupplier(retryContext, helloWorldService::returnHelloWorld);
// When
Try<String> result = Try.of(retryableSupplier);
// Then the helloWorldService should be invoked only once, because the exception should be rethrown immediately.
BDDMockito.then(helloWorldService).should(Mockito.times(1)).returnHelloWorld();
// and the result should be a failure
Assertions.assertThat(result.isFailure()).isTrue();
// and the returned exception should be of type RuntimeException
Assertions.assertThat(result.failed().get()).isInstanceOf(WebServiceException.class);
Assertions.assertThat(sleptTime).isEqualTo(0);
}
@Test
public void shouldReturnAfterThreeAttemptsAndRecover() {
// Given the HelloWorldService throws an exception
BDDMockito.given(helloWorldService.returnHelloWorld()).willThrow(new WebServiceException("BAM!"));
// Create a Retry with default configuration
Retry retryContext = Retry.ofDefaults("id");
// Decorate the invocation of the HelloWorldService
CheckedFunction0<String> retryableSupplier = Retry.decorateCheckedSupplier(retryContext, helloWorldService::returnHelloWorld);
// When
Try<String> result = Try.of(retryableSupplier).recover((throwable) -> "Hello world from recovery function");
// Then the helloWorldService should be invoked 3 times
BDDMockito.then(helloWorldService).should(Mockito.times(3)).returnHelloWorld();
// and the returned exception should be of type RuntimeException
Assertions.assertThat(result.get()).isEqualTo("Hello world from recovery function");
Assertions.assertThat(sleptTime).isEqualTo(RetryConfig.DEFAULT_WAIT_DURATION*2);
}
@Test
public void shouldTakeIntoAccountBackoffFunction() {
// Given the HelloWorldService throws an exception
BDDMockito.given(helloWorldService.returnHelloWorld()).willThrow(new WebServiceException("BAM!"));
// Create a Retry with a backoff function doubling the interval
RetryConfig config = RetryConfig.custom().intervalFunction(IntervalFunction.ofExponentialBackoff(500, 2.0)).build();
Retry retryContext = Retry.of("id", config);
// Decorate the invocation of the HelloWorldService
CheckedFunction0<String> retryableSupplier = Retry.decorateCheckedSupplier(retryContext, helloWorldService::returnHelloWorld);
// When
Try<String> result = Try.of(retryableSupplier);
// Then the slept time should be according to the backoff function
BDDMockito.then(helloWorldService).should(Mockito.times(3)).returnHelloWorld();
Assertions.assertThat(sleptTime).isEqualTo(
RetryConfig.DEFAULT_WAIT_DURATION +
RetryConfig.DEFAULT_WAIT_DURATION*2);
}
}