2323import java .util .Objects ;
2424import java .util .concurrent .atomic .AtomicInteger ;
2525import java .util .function .Consumer ;
26+ import java .util .function .Supplier ;
2627
2728import org .assertj .core .api .ThrowingConsumer ;
2829import org .junit .jupiter .api .BeforeEach ;
@@ -79,7 +80,7 @@ void checkRetryTemplateConfiguration() {
7980 }
8081
8182 @ Test
82- void retryWithImmediateSuccess () throws Exception {
83+ void retryableWithImmediateSuccess () throws Exception {
8384 AtomicInteger invocationCount = new AtomicInteger ();
8485 Retryable <String > retryable = () -> {
8586 invocationCount .incrementAndGet ();
@@ -97,7 +98,7 @@ void retryWithImmediateSuccess() throws Exception {
9798 }
9899
99100 @ Test
100- void retryWithInitialFailureAndZeroRetriesRetryPolicy () {
101+ void retryableWithInitialFailureAndZeroRetriesRetryPolicy () {
101102 RetryPolicy retryPolicy = throwable -> false ; // Zero retries
102103 RetryTemplate retryTemplate = new RetryTemplate (retryPolicy );
103104 retryTemplate .setRetryListener (retryListener );
@@ -121,9 +122,8 @@ void retryWithInitialFailureAndZeroRetriesRetryPolicy() {
121122 verifyNoMoreInteractions (retryListener );
122123 }
123124
124-
125125 @ Test
126- void retryWithInitialFailureAndZeroRetriesFixedBackOffPolicy () {
126+ void retryableWithInitialFailureAndZeroRetriesFixedBackOffPolicy () {
127127 RetryPolicy retryPolicy = RetryPolicy .withMaxRetries (0 );
128128
129129 RetryTemplate retryTemplate = new RetryTemplate (retryPolicy );
@@ -149,7 +149,7 @@ void retryWithInitialFailureAndZeroRetriesFixedBackOffPolicy() {
149149 }
150150
151151 @ Test
152- void retryWithInitialFailureAndZeroRetriesBackOffPolicyFromBuilder () {
152+ void retryableWithInitialFailureAndZeroRetriesBackOffPolicyFromBuilder () {
153153 RetryPolicy retryPolicy = RetryPolicy .builder ().maxRetries (0 ).build ();
154154
155155 RetryTemplate retryTemplate = new RetryTemplate (retryPolicy );
@@ -175,7 +175,7 @@ void retryWithInitialFailureAndZeroRetriesBackOffPolicyFromBuilder() {
175175 }
176176
177177 @ Test
178- void retryWithSuccessAfterInitialFailures () throws Exception {
178+ void retryableWithSuccessAfterInitialFailures () throws Exception {
179179 AtomicInteger invocationCount = new AtomicInteger ();
180180 Retryable <String > retryable = () -> {
181181 if (invocationCount .incrementAndGet () <= 2 ) {
@@ -201,7 +201,7 @@ void retryWithSuccessAfterInitialFailures() throws Exception {
201201 }
202202
203203 @ Test
204- void retryWithExhaustedPolicy () {
204+ void retryableWithExhaustedPolicy () {
205205 var invocationCount = new AtomicInteger ();
206206
207207 var retryable = new Retryable <>() {
@@ -239,7 +239,7 @@ public String getName() {
239239 }
240240
241241 @ Test
242- void retryWithInterruptionDuringSleep () {
242+ void retryableWithInterruptionDuringSleep () {
243243 Exception exception = new RuntimeException ("Boom!" );
244244 InterruptedException interruptedException = new InterruptedException ();
245245
@@ -269,7 +269,7 @@ void retryWithInterruptionDuringSleep() {
269269 }
270270
271271 @ Test
272- void retryWithFailingRetryableAndMultiplePredicates () {
272+ void retryableWithFailingRetryableAndMultiplePredicates () {
273273 var invocationCount = new AtomicInteger ();
274274 var exception = new NumberFormatException ("Boom!" );
275275
@@ -316,7 +316,7 @@ public String getName() {
316316 }
317317
318318 @ Test
319- void retryWithExceptionIncludes () {
319+ void retryableWithExceptionIncludes () {
320320 var invocationCount = new AtomicInteger ();
321321
322322 var retryable = new Retryable <>() {
@@ -387,7 +387,7 @@ public String getName() {
387387
388388 @ ParameterizedTest
389389 @ FieldSource ("includesAndExcludesRetryPolicies" )
390- void retryWithExceptionIncludesAndExcludes (RetryPolicy retryPolicy ) {
390+ void retryableWithExceptionIncludesAndExcludes (RetryPolicy retryPolicy ) {
391391 retryTemplate .setRetryPolicy (retryPolicy );
392392
393393 var invocationCount = new AtomicInteger ();
@@ -436,12 +436,105 @@ public String getName() {
436436 verifyNoMoreInteractions (retryListener );
437437 }
438438
439+ @ Test
440+ void supplierWithImmediateSuccess () {
441+ AtomicInteger invocationCount = new AtomicInteger ();
442+ Supplier <String > retryable = () -> {
443+ invocationCount .incrementAndGet ();
444+ return "always succeeds" ;
445+ };
446+
447+ assertThat (invocationCount ).hasValue (0 );
448+ assertThat (retryTemplate .invoke (retryable )).isEqualTo ("always succeeds" );
449+ assertThat (invocationCount ).hasValue (1 );
450+
451+ // RetryListener interactions:
452+ inOrder .verify (retryListener ).onRetryableExecution (eq (retryPolicy ),
453+ argThat (r -> r .getName ().equals (retryable .getClass ().getName ())),
454+ argThat (state -> state .isSuccessful () && state .getRetryCount () == 0 ));
455+ verifyNoMoreInteractions (retryListener );
456+ }
457+
458+ @ Test
459+ void supplierWithSuccessAfterInitialFailures () {
460+ AtomicInteger invocationCount = new AtomicInteger ();
461+ Supplier <String > retryable = () -> {
462+ if (invocationCount .incrementAndGet () <= 2 ) {
463+ throw new CustomException ("Boom " + invocationCount .get ());
464+ }
465+ return "finally succeeded" ;
466+ };
467+
468+ assertThat (invocationCount ).hasValue (0 );
469+ assertThat (retryTemplate .invoke (retryable )).isEqualTo ("finally succeeded" );
470+ assertThat (invocationCount ).hasValue (3 );
471+
472+ // RetryListener interactions:
473+ inOrder .verify (retryListener ).onRetryableExecution (eq (retryPolicy ),
474+ argThat (r -> r .getName ().equals (retryable .getClass ().getName ())),
475+ any (RetryState .class ));
476+ inOrder .verify (retryListener ).beforeRetry (eq (retryPolicy ),
477+ argThat (r -> r .getName ().equals (retryable .getClass ().getName ())));
478+ inOrder .verify (retryListener ).onRetryFailure (eq (retryPolicy ),
479+ argThat (r -> r .getName ().equals (retryable .getClass ().getName ())),
480+ eq (new CustomException ("Boom 2" )));
481+ inOrder .verify (retryListener ).onRetryableExecution (eq (retryPolicy ),
482+ argThat (r -> r .getName ().equals (retryable .getClass ().getName ())),
483+ any (RetryState .class ));
484+ inOrder .verify (retryListener ).beforeRetry (eq (retryPolicy ),
485+ argThat (r -> r .getName ().equals (retryable .getClass ().getName ())));
486+ inOrder .verify (retryListener ).onRetrySuccess (eq (retryPolicy ),
487+ argThat (r -> r .getName ().equals (retryable .getClass ().getName ())),
488+ eq ("finally succeeded" ));
489+ inOrder .verify (retryListener ).onRetryableExecution (eq (retryPolicy ),
490+ argThat (r -> r .getName ().equals (retryable .getClass ().getName ())),
491+ argThat (state -> state .isSuccessful () && state .getRetryCount () == 2 ));
492+ verifyNoMoreInteractions (retryListener );
493+ }
494+
495+ @ Test
496+ void supplierWithExhaustedPolicy () {
497+ AtomicInteger invocationCount = new AtomicInteger ();
498+ Supplier <String > retryable = () -> {
499+ throw new CustomException ("Boom " + invocationCount .incrementAndGet ());
500+ };
501+
502+ assertThat (invocationCount ).hasValue (0 );
503+ assertThatExceptionOfType (CustomException .class )
504+ .isThrownBy (() -> retryTemplate .invoke (retryable ))
505+ .withMessage ("Boom 4" )
506+ .satisfies (throwable -> {
507+ var counter = new AtomicInteger (1 );
508+ repeat (3 , () -> {
509+ inOrder .verify (retryListener ).onRetryableExecution (eq (retryPolicy ),
510+ argThat (r -> r .getName ().equals (retryable .getClass ().getName ())),
511+ any (RetryState .class ));
512+ inOrder .verify (retryListener ).beforeRetry (eq (retryPolicy ),
513+ argThat (r -> r .getName ().equals (retryable .getClass ().getName ())));
514+ inOrder .verify (retryListener ).onRetryFailure (eq (retryPolicy ),
515+ argThat (r -> r .getName ().equals (retryable .getClass ().getName ())),
516+ eq (new CustomException ("Boom " + counter .incrementAndGet ())));
517+ });
518+ inOrder .verify (retryListener ).onRetryableExecution (eq (retryPolicy ),
519+ argThat (r -> r .getName ().equals (retryable .getClass ().getName ())),
520+ argThat (state -> !state .isSuccessful () && state .getRetryCount () == 3 ));
521+ inOrder .verify (retryListener ).onRetryPolicyExhaustion (eq (retryPolicy ),
522+ argThat (r -> r .getName ().equals (retryable .getClass ().getName ())),
523+ argThat (t -> t .getMessage ().equals ("Retry policy for operation '" +
524+ retryable .getClass ().getName () + "' exhausted; aborting execution" )));
525+ });
526+ // 4 = 1 initial invocation + 3 retry attempts
527+ assertThat (invocationCount ).hasValue (4 );
528+
529+ verifyNoMoreInteractions (retryListener );
530+ }
531+
439532
440533 @ Nested
441534 class TimeoutTests {
442535
443536 @ Test
444- void retryWithImmediateSuccessAndTimeoutExceeded () throws Exception {
537+ void retryableWithImmediateSuccessAndTimeoutExceeded () throws Exception {
445538 RetryPolicy retryPolicy = RetryPolicy .builder ().timeout (Duration .ofMillis (10 )).build ();
446539 RetryTemplate retryTemplate = new RetryTemplate (retryPolicy );
447540 retryTemplate .setRetryListener (retryListener );
@@ -464,7 +557,7 @@ void retryWithImmediateSuccessAndTimeoutExceeded() throws Exception {
464557 }
465558
466559 @ Test
467- void retryWithInitialFailureAndZeroRetriesRetryPolicyAndTimeoutExceeded () {
560+ void retryableWithInitialFailureAndZeroRetriesRetryPolicyAndTimeoutExceeded () {
468561 RetryPolicy retryPolicy = RetryPolicy .builder ()
469562 .timeout (Duration .ofMillis (10 ))
470563 .predicate (throwable -> false ) // Zero retries
@@ -492,7 +585,7 @@ void retryWithInitialFailureAndZeroRetriesRetryPolicyAndTimeoutExceeded() {
492585 }
493586
494587 @ Test
495- void retryWithTimeoutExceededAfterInitialFailure () {
588+ void retryableWithTimeoutExceededAfterInitialFailure () {
496589 RetryPolicy retryPolicy = RetryPolicy .builder ()
497590 .timeout (Duration .ofMillis (10 ))
498591 .delay (Duration .ZERO )
@@ -521,7 +614,7 @@ void retryWithTimeoutExceededAfterInitialFailure() {
521614 }
522615
523616 @ Test
524- void retryWithTimeoutExceededAfterFirstDelayButBeforeFirstRetry () {
617+ void retryableWithTimeoutExceededAfterFirstDelayButBeforeFirstRetry () {
525618 RetryPolicy retryPolicy = RetryPolicy .builder ()
526619 .timeout (Duration .ofMillis (20 ))
527620 .delay (Duration .ofMillis (100 )) // Delay > Timeout
@@ -552,7 +645,7 @@ void retryWithTimeoutExceededAfterFirstDelayButBeforeFirstRetry() {
552645 }
553646
554647 @ Test
555- void retryWithTimeoutExceededAfterFirstRetry () {
648+ void retryableWithTimeoutExceededAfterFirstRetry () {
556649 RetryPolicy retryPolicy = RetryPolicy .builder ()
557650 .timeout (Duration .ofMillis (20 ))
558651 .delay (Duration .ZERO )
@@ -589,7 +682,7 @@ void retryWithTimeoutExceededAfterFirstRetry() {
589682 }
590683
591684 @ Test
592- void retryWithTimeoutExceededAfterSecondRetry () {
685+ void retryableWithTimeoutExceededAfterSecondRetry () {
593686 RetryPolicy retryPolicy = RetryPolicy .builder ()
594687 .timeout (Duration .ofMillis (20 ))
595688 .delay (Duration .ZERO )
0 commit comments