CtrlK
BlogDocsLog inGet started
Tessl Logo

giuseppe-trisciuoglio/developer-kit

Comprehensive developer toolkit providing reusable skills for Java/Spring Boot, TypeScript/NestJS/React/Next.js, Python, PHP, AWS CloudFormation, AI/RAG, DevOps, and more.

82

Quality

82%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Risky

Do not use without reviewing

Validation failed for skills in this tile
One or more skills have errors that need to be fixed before they can move to Implementation and Discovery review.
Overview
Quality
Evals
Security
Files

testing-patterns.mdplugins/developer-kit-java/skills/spring-boot-resilience4j/references/

Resilience4j Testing Patterns

Circuit Breaker Testing

Testing State Transitions

@SpringBootTest
class CircuitBreakerStateTest {

    @Autowired
    private PaymentService paymentService;

    @MockBean
    private RestTemplate restTemplate;

    @Test
    void shouldTransitionToOpenAfterFailures() {
        // Simulate repeated failures
        when(restTemplate.postForObject(anyString(), any(), eq(PaymentResponse.class)))
            .thenThrow(new HttpServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR));

        // Trigger failures to exceed threshold
        for (int i = 0; i < 5; i++) {
            assertThatThrownBy(() -> paymentService.processPayment(new PaymentRequest()))
                .isInstanceOf(HttpServerErrorException.class);
        }

        // Circuit should be open - fallback executes
        PaymentResponse response = paymentService.processPayment(new PaymentRequest());
        assertThat(response.getStatus()).isEqualTo("PENDING");
    }

    @Test
    void shouldExecuteFallbackWhenCircuitOpen() {
        when(restTemplate.postForObject(anyString(), any(), eq(PaymentResponse.class)))
            .thenThrow(new RuntimeException("Service unavailable"));

        // Force failures to open circuit
        for (int i = 0; i < 5; i++) {
            try {
                paymentService.processPayment(new PaymentRequest());
            } catch (Exception ignored) {}
        }

        // Circuit is open, fallback provides response
        PaymentResponse response = paymentService.processPayment(new PaymentRequest());
        assertThat(response.getStatus()).isEqualTo("PENDING");
        assertThat(response.getMessage()).contains("temporarily unavailable");
    }
}

Testing Circuit States Directly

@SpringBootTest
class CircuitBreakerDirectStateTest {

    @Autowired
    private CircuitBreakerRegistry circuitBreakerRegistry;

    @Test
    void shouldManuallyOpenAndCloseCircuit() {
        CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("paymentService");

        assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED);

        // Manually open circuit
        circuitBreaker.transitionToOpenState();
        assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.OPEN);

        // Manually close circuit
        circuitBreaker.transitionToClosedState();
        assertThat(circuitBreaker.getState()).isEqualTo(CircuitBreaker.State.CLOSED);
    }
}

Retry Testing

Testing Retry Attempts

@SpringBootTest
@AutoConfigureWireMock(port = 0)
class RetryTest {

    @Autowired
    private OrderService orderService;

    @Test
    void shouldRetryOnTransientFailure() {
        // Setup: First two calls fail, third succeeds
        stubFor(post("/orders")
            .inScenario("Retry Scenario")
            .whenScenarioStateIs(STARTED)
            .willReturn(serverError())
            .willSetStateTo("First Failure"));

        stubFor(post("/orders")
            .inScenario("Retry Scenario")
            .whenScenarioStateIs("First Failure")
            .willReturn(serverError())
            .willSetStateTo("Second Failure"));

        stubFor(post("/orders")
            .inScenario("Retry Scenario")
            .whenScenarioStateIs("Second Failure")
            .willReturn(ok().withBody("""
                {"id":1,"status":"CREATED"}
                """)));

        Order order = orderService.createOrder(new OrderRequest());

        assertThat(order.getId()).isEqualTo(1L);
        assertThat(order.getStatus()).isEqualTo("CREATED");

        // Verify exactly 3 calls were made
        verify(exactly(3), postRequestedFor(urlEqualTo("/orders")));
    }

    @Test
    void shouldThrowExceptionAfterMaxRetries() {
        stubFor(post("/orders").willReturn(serverError()));

        assertThatThrownBy(() -> orderService.createOrder(new OrderRequest()))
            .isInstanceOf(Exception.class);

        // Verify retry attempts (maxAttempts = 3)
        verify(atLeast(3), postRequestedFor(urlEqualTo("/orders")));
    }
}

Rate Limiter Testing

Testing Rate Limit Enforcement

@SpringBootTest
class RateLimiterTest {

    @Autowired
    private NotificationService notificationService;

    @Test
    void shouldRejectRequestsExceedingRateLimit() {
        // Configuration: 5 permits per second

        // First 5 requests should succeed
        for (int i = 0; i < 5; i++) {
            notificationService.sendEmail(createEmailRequest(i));
        }

        // 6th request should fail immediately (no timeout)
        assertThatThrownBy(() ->
            notificationService.sendEmail(createEmailRequest(6))
        ).isInstanceOf(RequestNotPermitted.class);
    }

    @Test
    void shouldAlowRequestsAfterWindowReset() throws InterruptedException {
        // First batch of requests
        for (int i = 0; i < 5; i++) {
            notificationService.sendEmail(createEmailRequest(i));
        }

        // Wait for refresh period (1 second)
        Thread.sleep(1100);

        // Should succeed - window has reset
        notificationService.sendEmail(createEmailRequest(5));
    }

    private EmailRequest createEmailRequest(int id) {
        return EmailRequest.builder()
            .to("user" + id + "@example.com")
            .subject("Test " + id)
            .build();
    }
}

Bulkhead Testing

Testing Semaphore Bulkhead

@SpringBootTest
class BulkheadSemaphoreTest {

    @Autowired
    private ReportService reportService;

    @Test
    void shouldLimitConcurrentCalls() {
        // Configuration: maxConcurrentCalls = 5

        CountDownLatch latch = new CountDownLatch(5);
        List<CompletableFuture<Report>> futures = new ArrayList<>();

        // Submit 5 concurrent calls
        for (int i = 0; i < 5; i++) {
            futures.add(CompletableFuture.supplyAsync(() -> {
                latch.countDown();
                return reportService.generateReport(new ReportRequest());
            }));
        }

        // 6th call should be rejected
        assertThatThrownBy(() ->
            reportService.generateReport(new ReportRequest())
        ).isInstanceOf(BulkheadFullException.class);

        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
    }
}

Testing Thread Pool Bulkhead

@SpringBootTest
class BulkheadThreadPoolTest {

    @Autowired
    private AnalyticsService analyticsService;

    @Test
    void shouldUseThreadPoolForAsync() {
        // Configuration: threadPoolSize = 2, queueCapacity = 100

        List<CompletableFuture<AnalyticsResult>> futures = new ArrayList<>();

        for (int i = 0; i < 10; i++) {
            futures.add(analyticsService.runAnalytics(new AnalyticsRequest()));
        }

        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

        for (CompletableFuture<AnalyticsResult> future : futures) {
            assertThat(future.join()).isNotNull();
        }
    }
}

Time Limiter Testing

Testing Timeout Enforcement

@SpringBootTest
class TimeLimiterTest {

    @Autowired
    private SearchService searchService;

    @Test
    void shouldTimeoutExceededOperations() {
        // Configuration: timeoutDuration = 1s

        SearchQuery slowQuery = new SearchQuery();
        slowQuery.setSimulatedDelay(Duration.ofSeconds(2));

        assertThatThrownBy(() ->
            searchService.search(slowQuery).get()
        ).hasCauseInstanceOf(TimeoutException.class);
    }

    @Test
    void shouldReturnFallbackOnTimeout() {
        SearchQuery slowQuery = new SearchQuery();
        slowQuery.setSimulatedDelay(Duration.ofSeconds(2));

        CompletableFuture<SearchResults> result = searchService.search(slowQuery);
        SearchResults results = result.join();

        assertThat(results).isNotNull();
        assertThat(results.isTimedOut()).isTrue();
        assertThat(results.getMessage()).contains("timed out");
    }
}

Fallback Method Signature Validation

Correct Fallback Signatures

@Service
public class PaymentService {

    @CircuitBreaker(name = "payment", fallbackMethod = "paymentFallback")
    public PaymentResponse processPayment(PaymentRequest request) {
        // method body
    }

    // CORRECT: Matches return type and parameters + Exception
    private PaymentResponse paymentFallback(PaymentRequest request, Exception ex) {
        // fallback logic
    }

    @Retry(name = "product")
    public Product getProduct(String productId) {
        // method body
    }

    // CORRECT: Can omit Exception parameter
    private Product getProductFallback(String productId) {
        // fallback logic
    }
}

Common Fallback Signature Errors

@CircuitBreaker(name = "service", fallbackMethod = "fallback")
public String processData(Long id) { }

// WRONG: Missing parameter
public String fallback(Exception ex) { }

// WRONG: Wrong return type
public void fallback(Long id, Exception ex) { }

// WRONG: Wrong parameter type
public String fallback(String id, Exception ex) { }

// CORRECT:
public String fallback(Long id, Exception ex) { }

Integration Testing Configuration

Test Configuration Profile

# application-test.yml
resilience4j:
  circuitbreaker:
    instances:
      testService:
        registerHealthIndicator: false
        slidingWindowSize: 5
        minimumNumberOfCalls: 3
        failureRateThreshold: 50
        waitDurationInOpenState: 100ms

  retry:
    instances:
      testService:
        maxAttempts: 2
        waitDuration: 10ms

  ratelimiter:
    instances:
      testService:
        limitForPeriod: 10
        limitRefreshPeriod: 1s
        timeoutDuration: 10ms

Test Helper Methods

@TestConfiguration
public class ResilienceTestConfig {

    public static void openCircuitBreaker(CircuitBreaker circuitBreaker) {
        circuitBreaker.transitionToOpenState();
    }

    public static void closeCircuitBreaker(CircuitBreaker circuitBreaker) {
        circuitBreaker.transitionToClosedState();
    }

    public static void simulateFailures(
            CircuitBreaker circuitBreaker,
            int numberOfFailures) {
        for (int i = 0; i < numberOfFailures; i++) {
            try {
                circuitBreaker.executeSupplier(() -> {
                    throw new RuntimeException("Simulated failure");
                });
            } catch (Exception ignored) {}
        }
    }

    public static void resetCircuitBreaker(CircuitBreaker circuitBreaker) {
        circuitBreaker.transitionToClosedState();
    }
}

Common Testing Mistakes

Mistake 1: Not Waiting for Sliding Window

// WRONG: Circuit might not open yet
for (int i = 0; i < 3; i++) {
    try { service.call(); } catch (Exception e) {}
}
assertThat(circuit.getState()).isEqualTo(CircuitBreaker.State.OPEN); // May fail!

// CORRECT: Exceed minimumNumberOfCalls before checking
for (int i = 0; i < 5; i++) {  // minimumNumberOfCalls = 5
    try { service.call(); } catch (Exception e) {}
}
assertThat(circuit.getState()).isEqualTo(CircuitBreaker.State.OPEN);

Mistake 2: Incorrect Fallback Method Access

// WRONG: Fallback method is private, not accessible by AOP
@CircuitBreaker(name = "service", fallbackMethod = "fallback")
public String process(String data) { }

private String fallback(String data, Exception ex) { }  // Private - won't work!

// CORRECT: Package-private or protected
protected String fallback(String data, Exception ex) { }

Mistake 3: Not Mocking External Dependencies

// WRONG: Circuit breaker might open due to real network calls
@SpringBootTest
class ServiceTest {
    @Autowired
    private ServiceWithCircuitBreaker service;

    // Missing @MockBean for external service

    @Test
    void test() {
        // Real network calls - unpredictable
    }
}

// CORRECT: Mock external dependencies
@SpringBootTest
class ServiceTest {
    @Autowired
    private ServiceWithCircuitBreaker service;

    @MockBean
    private ExternalService externalService;

    @Test
    void test() {
        when(externalService.call()).thenThrow(new RuntimeException());
        // Predictable failure
    }
}

Performance Considerations

Memory Usage in Tests

  • COUNT_BASED sliding window: Stores last N call outcomes in memory
  • TIME_BASED sliding window: May require more memory for high-throughput services
  • Use smaller slidingWindowSize in tests to reduce memory footprint

Timeout Configuration for Tests

resilience4j:
  timelimiter:
    instances:
      testService:
        timeoutDuration: 2s    # Longer timeout for slower CI/CD environments

  circuitbreaker:
    instances:
      testService:
        waitDurationInOpenState: 100ms  # Shorter for faster test execution

Avoiding Test Flakiness

  • Set deterministic timeouts based on CI/CD environment
  • Use @ActiveProfiles("test") for test-specific configurations
  • Reset circuit breaker state between tests when needed
  • Mock external services consistently

plugins

developer-kit-java

skills

README.md

tile.json