Overhaul tests for RetryTemplate
Build and Deploy Snapshot / Build and Deploy Snapshot (push) Waiting to run Details
Build and Deploy Snapshot / Verify (push) Blocked by required conditions Details
Deploy Docs / Dispatch docs deployment (push) Waiting to run Details

This commit is contained in:
Sam Brannen 2025-06-12 17:34:41 +02:00
parent ff167aafa2
commit 082eb607ec
2 changed files with 61 additions and 55 deletions

View File

@ -34,7 +34,7 @@ public final class PredicateRetryPolicy implements RetryPolicy {
/**
* Create a new {@code PredicateRetryPolicy} with the given predicate.
* Create a new {@code PredicateRetryPolicy} with the given {@link Predicate}.
* @param predicate the predicate to use for determining whether to retry an
* operation based on a given {@link Throwable}
*/

View File

@ -17,7 +17,9 @@
package org.springframework.core.retry;
import java.time.Duration;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.util.backoff.FixedBackOff;
@ -36,95 +38,99 @@ class RetryTemplateTests {
private final RetryTemplate retryTemplate = new RetryTemplate();
@Test
void retryWithSuccess() throws Exception {
Retryable<String> retryable = new Retryable<>() {
int failure;
@Override
public String execute() throws Exception {
if (failure++ < 2) {
throw new Exception("Error while invoking greeting service");
}
return "hello world";
}
@Override
public String getName() {
return "greeting service";
}
};
retryTemplate.setBackOffPolicy(new FixedBackOff(Duration.ofMillis(10)));
assertThat(retryTemplate.execute(retryable)).isEqualTo("hello world");
@BeforeEach
void configureTemplate() {
this.retryTemplate.setBackOffPolicy(new FixedBackOff(Duration.ofMillis(10)));
}
@Test
void retryWithFailure() {
Exception exception = new Exception("Error while invoking greeting service");
void retryWithImmediateSuccess() throws Exception {
AtomicInteger invocationCount = new AtomicInteger();
Retryable<String> retryable = () -> {
invocationCount.incrementAndGet();
return "always succeeds";
};
assertThat(invocationCount).hasValue(0);
assertThat(retryTemplate.execute(retryable)).isEqualTo("always succeeds");
assertThat(invocationCount).hasValue(1);
}
@Test
void retryWithSuccessAfterInitialFailures() throws Exception {
AtomicInteger invocationCount = new AtomicInteger();
Retryable<String> retryable = () -> {
if (invocationCount.incrementAndGet() <= 2) {
throw new Exception("Boom!");
}
return "finally succeeded";
};
assertThat(invocationCount).hasValue(0);
assertThat(retryTemplate.execute(retryable)).isEqualTo("finally succeeded");
assertThat(invocationCount).hasValue(3);
}
@Test
void retryWithExhaustedPolicy() {
AtomicInteger invocationCount = new AtomicInteger();
RuntimeException exception = new RuntimeException("Boom!");
Retryable<String> retryable = new Retryable<>() {
@Override
public String execute() throws Exception {
public String execute() {
invocationCount.incrementAndGet();
throw exception;
}
@Override
public String getName() {
return "greeting service";
return "test";
}
};
retryTemplate.setBackOffPolicy(new FixedBackOff(Duration.ofMillis(10)));
assertThat(invocationCount).hasValue(0);
assertThatExceptionOfType(RetryException.class)
.isThrownBy(() -> retryTemplate.execute(retryable))
.withMessage("Retry policy for operation 'greeting service' exhausted; aborting execution")
.withMessage("Retry policy for operation 'test' exhausted; aborting execution")
.withCause(exception);
// 4 = 1 initial invocation + 3 retry attempts
assertThat(invocationCount).hasValue(4);
}
@Test
void retrySpecificException() {
@SuppressWarnings("serial")
class TechnicalException extends Exception {
public TechnicalException(String message) {
super(message);
}
}
TechnicalException technicalException = new TechnicalException("Error while invoking greeting service");
void retryWithFailingRetryableAndCustomRetryPolicy() {
AtomicInteger invocationCount = new AtomicInteger();
RuntimeException exception = new NumberFormatException();
Retryable<String> retryable = new Retryable<>() {
@Override
public String execute() throws TechnicalException {
throw technicalException;
public String execute() {
invocationCount.incrementAndGet();
throw exception;
}
@Override
public String getName() {
return "greeting service";
}
};
RetryPolicy retryPolicy = () -> new RetryExecution() {
int retryAttempts;
@Override
public boolean shouldRetry(Throwable throwable) {
return (this.retryAttempts++ < 3 && throwable instanceof TechnicalException);
return "always fails";
}
};
AtomicInteger retryCount = new AtomicInteger();
// Custom RetryPolicy that only retries for a NumberFormatException and max 5 retry attempts.
RetryPolicy retryPolicy = () -> throwable -> (retryCount.incrementAndGet() <= 5 && throwable instanceof NumberFormatException);
retryTemplate.setRetryPolicy(retryPolicy);
retryTemplate.setBackOffPolicy(new FixedBackOff(Duration.ofMillis(10)));
assertThat(invocationCount).hasValue(0);
assertThat(retryCount).hasValue(0);
assertThatExceptionOfType(RetryException.class)
.isThrownBy(() -> retryTemplate.execute(retryable))
.withMessage("Retry policy for operation 'greeting service' exhausted; aborting execution")
.withCause(technicalException);
.withMessage("Retry policy for operation 'always fails' exhausted; aborting execution")
.withCause(exception);
// 6 = 1 initial invocation + 5 retry attempts
assertThat(invocationCount).hasValue(6);
assertThat(retryCount).hasValue(6);
}
}