package com.nurkiewicz.reactor; import com.nurkiewicz.reactor.samples.CacheServer; import com.nurkiewicz.reactor.user.User; import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; import java.time.Duration; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; import static org.assertj.core.api.Assertions.assertThat; @Ignore public class R045_ErrorHandling { private static final Logger log = LoggerFactory.getLogger(R045_ErrorHandling.class); @Test public void onErrorReturn() throws Exception { //given final Mono<String> err = Mono.error(new RuntimeException("Opps")); //when final Mono<String> withFallback = err.onErrorReturn("Fallback"); //then err .as(StepVerifier::create) .verifyErrorMessage("Opps"); withFallback .as(StepVerifier::create) .expectNext("Fallback") .verifyComplete(); } /** * TODO Where's the 'Failed' exception? Add some logging */ @Test public void onErrorResume() throws Exception { //given AtomicBoolean cheapFlag = new AtomicBoolean(); AtomicBoolean expensiveFlag = new AtomicBoolean(); Mono<String> cheapButDangerous = Mono.fromCallable(() -> { cheapFlag.set(true); throw new RuntimeException("Failed"); }); Mono<String> expensive = Mono.fromCallable(() -> { expensiveFlag.set(true); return "Expensive"; }); //when final Mono<String> withError = cheapButDangerous .onErrorResume(e -> expensive); //then withError .as(StepVerifier::create) .expectNext("Expensive") .verifyComplete(); assertThat(cheapFlag).isTrue(); assertThat(expensiveFlag).isTrue(); } /** * TODO Return different value for {@link IllegalStateException} and different for {@link IllegalArgumentException} * @throws Exception */ @Test public void handleExceptionsDifferently() throws Exception { handle(danger(1)) .as(StepVerifier::create) .expectNext(-1) .verifyComplete(); handle(danger(2)) .as(StepVerifier::create) .expectNext(-2) .verifyComplete(); handle(danger(3)) .as(StepVerifier::create) .verifyErrorMessage("Other: 3"); } /** * TODO Add error handling * @see Mono#onErrorResume(Function) */ private Mono<Integer> handle(Mono<Integer> careful) { return careful; } private Mono<Integer> danger(int id) { return Mono.fromCallable(() -> { switch(id) { case 1: throw new IllegalArgumentException("One"); case 2: throw new IllegalStateException("Two"); default: throw new RuntimeException("Other: " + id); } }); } @Test public void simpleRetry() throws Exception { //given CacheServer cacheServer = new CacheServer("foo.com", Duration.ofMillis(500), 1); //when final Mono<String> retried = cacheServer .findBy(1) .retry(4); //then retried .as(StepVerifier::create) .verifyErrorMessage("Simulated fault"); } /** * TODO Why this test never finishes? Add some logging and fix {@link #broken()} method. */ @Test public void fixEagerMono() throws Exception { //given final Mono<User> mono = broken(); //when final Mono<User> retried = mono.retry(); //then retried .as(StepVerifier::create) .expectNext(new User(1)) .verifyComplete(); } Mono<User> broken() { if (ThreadLocalRandom.current().nextDouble() > 0.1) { return Mono.error(new RuntimeException("Opps")); } return Mono.just(new User(1)); } @Test public void retryWithExponentialBackoff() throws Exception { Mono .error(new RuntimeException("Opps")) .doOnError(x -> log.warn("Exception: {}", x.toString())) .retryBackoff(20, Duration.ofMillis(100), Duration.ofSeconds(2), 1) .subscribe(); TimeUnit.SECONDS.sleep(5); } }