Как создавать модульные тесты для методов, аннотированных @Circuitbreaker

Я реализовал resilience4j в своем проекте с помощью стартера Spring Boot2 (https://resilience4j.readme.io/docs/getting-started-3).

Я аннотировал метод с помощью @CircuitBreaker, который использует http-клиент для вызова внешней службы, и автоматический выключатель работает нормально, включая его откат.

Я хотел бы добавить для него модульные тесты, но когда я запускаю тест, пытаясь имитировать откат, ничего не происходит - возникает исключение, но не обрабатывается механизмом выключателя.

Я нашел несколько примеров, использующих его метрики, но в моем случае это бесполезно.

Есть предположения?

Вот фрагмент моего клиента:

@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";
    }

person Eduardo Lima    schedule 30.03.2020    source источник
comment
как вы называете этот метод поиском?   -  person pvpkiran    schedule 30.03.2020
comment
Просто вызов client.search(value) из службы, которая вызывается контроллером   -  person Eduardo Lima    schedule 30.03.2020
comment
это проблема. Если вы вызываете службу напрямую, то магия пружины (аспекты) не имеет значения. и, следовательно, автоматический выключатель не работает. Во время нормального выполнения, когда поток переходит от одного файла к другому (от контроллера к классу обслуживания), Spring перехватывает вызов и выполняет множество действий. Если вы звоните напрямую, это не сработает. Вызов должен перейти от весеннего боба к весеннему бобу   -  person pvpkiran    schedule 30.03.2020
comment
хм ... имеет смысл. Тогда воспользуюсь интеграционными тестами! Спасибо за оперативную поддержку!   -  person Eduardo Lima    schedule 30.03.2020
comment
@ user2541537 как вы прошли этот интеграционный тест? У меня такая же проблема, и я буду признателен, если вы поделитесь тем, что сработало для вас, как вы настроили тест интеграции и т. Д. Спасибо   -  person Urosh T.    schedule 31.03.2020
comment
Привет, Урош Т. Я только что добавил ответ, надеюсь, он вам подходит.   -  person Eduardo Lima    schedule 01.04.2020


Ответы (2)


Как andres и pvpkiran упомянул / объяснил, мне пришлось добавить интеграционный тест.

Вы можете добиться этого, просто добавив аннотацию @SpringBootTest в ваш тестовый класс, он загрузит контейнер с контекстом Spring.

Я также автоматически подключил CircuitBreakerRegistry, чтобы сбрасывать автоматический выключатель перед каждым тестом, чтобы я мог гарантировать чистый тест. Для издевательства / шпионажа / проверки я использовал Mockito из стартера Spring boot test (spring-boot-starter-test).

Вот как мне удалось протестировать резервные методы:

@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));
    }

}

Я надеюсь, что ошибки компиляции нет, так как мне пришлось удалить некоторые несущественные вещи.

person Eduardo Lima    schedule 01.04.2020

Вы не должны тестировать @CircuitBreaker в модульном тесте, поскольку он включает более одного класса. Скорее используйте интеграционный тест.

person Andres    schedule 30.03.2020