### Question:
ConditionalRetry implements Retry { @Override public Response execute( String methodName, Request request, Function<Request, Response> callback) throws Throwable { int attempts = 0; Response response = null; Throwable lastException = null; boolean shouldRetry = true; while (shouldRetry) { attempts++; try { response = callback.apply(request); } catch (Exception ex) { lastException = this.exceptionHandler.apply(ex); } RetryContext context = new RetryContext(attempts, lastException, response); shouldRetry = this.condition.test(context) && (attempts < this.maxAttempts); if (shouldRetry) { this.logger.logRetry(methodName, context); long interval = this.interval.getInterval(context); if (interval > 0) { try { Thread.sleep(interval); } catch (InterruptedException ie) { log.info("Retry interval was interrupted. Starting next retry attempt."); } } } } if (lastException != null) { throw lastException; } return response; } ConditionalRetry(
      int maxAttempts, RetryCondition condition, RetryInterval interval, Logger logger,
      ExceptionHandler exceptionHandler); static ConditionalRetryBuilder builder(
      int maxAttempts, Logger logger, ExceptionHandler exceptionHandler); @Override Response execute(
      String methodName, Request request, Function<Request, Response> callback);  }### Answer:
@Test void shouldStopRetry_andReturnLastResponse_whenConditionPass_andAttemptsExhausted() throws Throwable { RetryCondition condition = mock(RetryCondition.class); when(condition.test(any(RetryContext.class))).thenReturn(true); when(client.request(any(Request.class))).thenReturn(this.response); this.conditionalRetry = new ConditionalRetry(3, condition, retryContext -> 1L, logger, exceptionHandler); Response response = this.conditionalRetry.execute("test", this.request, client::request); verify(this.client, times(3)).request(any(Request.class)); verify(this.logger, times(2)).logRetry(anyString(), any(RetryContext.class)); assertThat(response).isNotNull(); }
@Test void shouldStopRetry_andThrowLastException_whenConditionPass_andAttemptsExhausted() { RetryCondition condition = mock(RetryCondition.class); when(condition.test(any(RetryContext.class))).thenReturn(true); when(client.request(any(Request.class))).thenReturn(this.response, this.response) .thenThrow(new RuntimeException("failed")); this.conditionalRetry = new ConditionalRetry(3, condition, retryContext -> 1L, logger, exceptionHandler); assertThrows(RuntimeException.class, () -> this.conditionalRetry.execute("test", this.request, client::request)); verify(this.client, times(3)).request(any(Request.class)); verify(this.logger, times(2)).logRetry(anyString(), any(RetryContext.class)); }
@Test void shouldNotRetry_whenConditionFails_onInitialAttempt() throws Throwable { RetryCondition condition = mock(RetryCondition.class); when(condition.test(any(RetryContext.class))).thenReturn(false); when(client.request(any(Request.class))).thenReturn(this.response); this.conditionalRetry = new ConditionalRetry(3, condition, retryContext -> 1L, logger, exceptionHandler); Response response = this.conditionalRetry.execute("test", this.request, client::request); verify(this.client, times(1)).request(any(Request.class)); verifyZeroInteractions(this.logger); assertThat(response).isNotNull(); }
@Test void shouldNotRetry_whenConditionFails_afterInitialAttempt() throws Throwable { RetryCondition condition = mock(RetryCondition.class); when(condition.test(any(RetryContext.class))).thenReturn(true, false); when(client.request(any(Request.class))).thenReturn(this.response); this.conditionalRetry = new ConditionalRetry(3, condition, retryContext -> 1L, logger, exceptionHandler); Response response = this.conditionalRetry.execute("test", this.request, client::request); verify(this.client, times(2)).request(any(Request.class)); verify(this.logger, times(1)).logRetry(anyString(), any(RetryContext.class)); assertThat(response).isNotNull(); }