Search code examples
spring-testspring-boot-testresilience4j

How to run the SpringBootTest with only a single bean and with included resilience4j annotations


I would like to run an integration test of a single bean with resilience4j annotated method in a spring boot app. My intent is to test resiliency of bean method calls while not loading the full spring context.

The setup is as follows:

Dependencies include the following:

io.github.resilience4j:resilience4j-spring-boot2
io.github.resilience4j:resilience4j-reactor
org.springframework.boot:spring-boot-starter-aop

The resilience4j time limited spring bean with method to test:

@Service
public class FooService {

    @TimeLimiter(name = "fooTimeLimiter")
    public FooResponse foo() {
        //entertain operation that might timeout
    }
}

Configuration:

resilience4j.timelimiter.instances.fooTimeLimiter.timeoutDuration=1s

And the test:

@SpringBootTest
@ContextConfiguration(classes = FooService.class)
public class FooServiceIT {

    @Autowired
    private FooService service;
    
    @MockBean
    private Bar bar;

    @Test
    void foo_timeout() {
        //setup mocks so the operation delays the output and shall end up with timeout
        
        Assertions.assertThrows(TimeoutException.class, () -> service.foo());
    }    
}

However, the TimeLimiterAdvice.proceed() is not entertained, no timeout exception is thrown and the test fails.

Same question has been asked here: Testing SpringBoot with annotation-style Resilience4j but there is no solution.

I tried both approaches - implement FooService interface and program directly using the concrete class. With the same result.

How can I achieve the time limiter annotation is taken into account in my test?

Edit: I even tried plain spring test (no spring boot) with the following setup:

@ExtendWith(SpringExtension.class)
@ContextConfiguration(loader = AnnotationConfigContextLoader.class)
public class FooServiceIT {

    @Configuration
    @Import({TimeLimiterConfiguration.class, FallbackConfiguration.class, SpelResolverConfiguration.class})
    static class ContextConfiguration {

        @Bean
        public FooService fooService() {
            //prepare bean;
        }

        @Bean
        public TimeLimiterConfigurationProperties timeLimiterConfigurationProperties() {
            return new TimeLimiterConfigurationProperties();
        }
    }

    @Autowired
    private FooService service;

    //tests...
}

Same result (i.e. no timeout exception).


Solution

  • When dealing with SpringBootTest and @CircuitBreaker, it was sufficient to add @EnableAspectJAutoProxy annotation to the test. After this change, the CircuitBreakerAspect was entertained and the test behaves as expected.

    In order to make @TimeLimiter working as expected, one need to add @Bulkhead annotation to the method as well.

    The updated method looks as follows:

    @Bulkhead(name = "fooBulkhead", type = Type.THREADPOOL)
    @CircuitBreaker(
            name = "fooCircuitBreaker",
            fallbackMethod = "fooFallback"
    )
    @TimeLimiter(
            name = "fooTimeLimiter"
    )
    public CompletableFuture<FooResponse> foo() {
        //...
    }
    

    and the test:

    @SpringBootTest(classes = FooService.class)
    @EnableAspectJAutoProxy
    @Import(value = {CircuitBreakerAutoConfiguration.class, TimeLimiterAutoConfiguration.class, BulkheadAutoConfiguration.class})
    public class FooServiceIT {
        //...
    }