6

I implemented resilience4j in my project using the Spring Boot2 starter (https://resilience4j.readme.io/docs/getting-started-3).

I annotated a method with @CircuitBreaker that uses http client for calling an external service and the circuit breaker is working fine - including its fallback.

I'd like to add unit tests for it but when I run a test trying to simulate the fallback, nothing happens - the exception is thrown but is not handled by the circuit breaker mechanism.

I've found some examples using its metrics but it is not useful in my case.

Any thoughts?

Here is a snippet of my client:

@CircuitBreaker(name = "MY_CICUIT_BREAKER", fallbackMethod = "fallback")
    public ResponseEntity<String> search(String value) {

        ResponseEntity<String> responseEntity = restTemplate.exchange(
                searchURL,
                HttpMethod.GET,
                new HttpEntity(new HttpHeaders()),
                String.class,
                value);
    }

public ResponseEntity<String> fallback(String value, ResourceAccessException ex) {
        return "fallback executed";
    }
Eduardo Lima
  • 316
  • 1
  • 2
  • 10
  • how are you calling this method search? – pvpkiran Mar 30 '20 at 13:33
  • Simply calling ```client.search(value)``` from a service, which is called by a controller – Eduardo Lima Mar 30 '20 at 13:36
  • that is the problem. If u call the service directly, then spring magic(aspects) doesn't some into picture . and hence circuit breaker functionality doesnt work. During normal execution when flow goes from one file to another(controller to service class) spring intercepts the call and does lot of things.Becos of which the whole thing works. In case you call directly then it doesnt work. The call has to go from a spring bean to spring bean – pvpkiran Mar 30 '20 at 13:39
  • hmm... makes sense. I will use integration tests then! Thanks for the prompt support ! – Eduardo Lima Mar 30 '20 at 13:42
  • @user2541537 how did you go about this integration test? I am having the same issue and would appreciate if you share what has worked for you, how did you setup the integration test, etc... Thanks – Urosh T. Mar 31 '20 at 07:41
  • Hey [Urosh T.](https://stackoverflow.com/users/5415018/urosh-t) I just added an answer, I hope it suits you. – Eduardo Lima Apr 01 '20 at 08:46

2 Answers2

5

As andres and pvpkiran mentioned/explained, I had to add a integration test.

You can achieve that basically adding @SpringBootTest annotation to your test class, it will bootstrap a container with spring context on it.

I also autowired CircuitBreakerRegistry in order to reset the circuit breaker before each test so I could guarantee a clean test. For mocking/spying/verifying I used Mockito from spring boot test starter (spring-boot-starter-test).

Here is how I managed to test the fallbacks methods:

@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = Application.class)
public class RestClientIntegrationTest {

    private final String SEARCH_VALUE = "1234567890";

    @MockBean( name = "myRealRestTemplateName")
    private RestTemplate restTemplate;

    @SpyBean
    private MyRestClient client;

    @Autowired
    private CircuitBreakerRegistry circuitBreakerRegistry;

    @BeforeEach
    public void setUp() {
        circuitBreakerRegistry.circuitBreaker("MY_CIRCUIT_BREAKER_NAME").reset();
    }

    @Test
    public void should_search_and_fallback_when_ResourceAccessException_is_thrown() {
        // prepare
        when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class), eq(SEARCH_VALUE)))
                .thenThrow(ResourceAccessException.class);

        String expectedResult = "expected result when fallback is called";

        // action
        String actualResult = client.search(SEARCH_VALUE);

        // assertion
        verify(client).fallback(eq(SEARCH_VALUE), any(ResourceAccessException.class));
        assertThat(actualResult, is(expectedResult));
    }

}

I hope there is no compilation error since I had to remove some non-relevant stuff.

Eduardo Lima
  • 316
  • 1
  • 2
  • 10
1

You shouldn't test @CircuitBreaker in a unit test as it involves more than one class. Rather use an integration test.

Andres
  • 10,561
  • 4
  • 45
  • 63
  • What you're saying makes sense.. However, how do you setup an integration test where you can control the state of a downstream service, especially in a CI/CD pipeline process? I'm asking cause I'm trying to do the same thing as OP - I'm sure I can trust the lib to function as expected but I want to make sure my code performs the fallback method appropriately. – rj2700 Apr 21 '22 at 22:55
  • 1
    As Fowler states, it's not really useful discussing if it's a unit or an integration test. It's more useful to discuss if it's a solitary or sociable test and I thought this might interest you as well - https://martinfowler.com/bliki/UnitTest.html – tdhulster Sep 20 '22 at 13:54
  • Use mock server (https://www.mock-server.com/) to emulate the downstream service. You con easily control the mock server responses etc so its a good fit for testing circuit breaker and retry scenarios. – Paul Keogh Sep 29 '22 at 09:54