Как выдать исключение в части ошибки реактивного вызова Spring WebClient? - PullRequest
0 голосов
/ 02 октября 2019

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

@Service
public class MyClass {

    private final WebClient webClient;

    public MatcherClient(@Value("${my.url}") final String myUrl) {
        this.webClient = WebClient.create(myUrl);
    }

    public void sendAsync(String request) {

        Mono<MyCustomResponse> result = webClient.post()
            .header(HttpHeaders.CONTENT_TYPE, "application/json")
            .body(BodyInserters.fromObject(request))
            .retrieve()
            .doOnError(throwable -> throw new CustomException(throwable.getMessage()))
            .subscribe(response -> log.info(response));

    }

}

Я также настроил модульный тест, ожидающий выброс CustomException. К сожалению, тест не пройден, и Исключение как бы обернуто в объект Mono. Вот также код теста для справки:

@Test(expected = CustomException.class)
public void testSendAsyncRethrowingException() {
    MockResponse mockResponse = new MockResponse()
        .setHeader(HttpHeaders.CONTENT_TYPE, "application/json")
        .setResponseCode(500).setBody("Server error");
    mockWebServer.enqueue(mockResponse);

    matcherService.matchAsync(track);
}

Я использую MockWebServer , чтобы смоделировать ошибку в тесте.

Итак, как мне реализовать часть doOnError или onError, если вызов для того, чтобы мой метод действительно выдал исключение?

Ответы [ 2 ]

1 голос
/ 03 октября 2019

Я бы посоветовал предоставить реактивный API, который возвращает Mono<Void> от веб-клиента, особенно если вы назовете свой метод «sendAsync». Это не асинхронно, если вам нужно заблокировать вызов / возврат. Если вы хотите предоставить альтернативу sendSync(), вы всегда можете вызвать ее sendAsync().block().

. Для преобразования исключения вы можете использовать выделенный оператор onErrorMap.

ДляСуть в том, что вы не можете на 100% тестировать асинхронный код с чисто императивными и синхронными конструкциями (например, аннотация JUnit Test(expected=?)). (хотя некоторые реактивные операторы не вызывают параллелизма, поэтому этот вид теста может иногда работать).

Вы также можете использовать .block() здесь (тестирование одноиз редких случаев, когда это вряд ли будет проблематично).

Но на вашем месте я бы привык использовать StepVerifier из reactor-test. Чтобы привести пример, который подводит итог моих рекомендаций:

@Service
public class MyClass {

    private final WebClient webClient;

    public MatcherClient(@Value("${my.url}") final String myUrl) {
        this.webClient = WebClient.create(myUrl);
    }

    public Mono<MyCustomResponse> sendAsync(String request) {
        return webClient.post()
            .header(HttpHeaders.CONTENT_TYPE, "application/json")
            .body(BodyInserters.fromObject(request))
            .retrieve()
            .onErrorMap(throwable -> new CustomException(throwable.getMessage()))
            //if you really need to hardcode that logging
            //(can also be done by users who decide to subscribe or further add operators)
            .doOnNext(response -> log.info(response));
    }
}

и тест:

@Test(expected = CustomException.class)
public void testSendAsyncRethrowingException() {
    MockResponse mockResponse = new MockResponse()
        .setHeader(HttpHeaders.CONTENT_TYPE, "application/json")
        .setResponseCode(500).setBody("Server error");
    mockWebServer.enqueue(mockResponse);

    //Monos are generally lazy, so the code below doesn't trigger any HTTP request yet
    Mono<MyCustomResponse> underTest = matcherService.matchAsync(track);

    StepVerifier.create(underTest)
    .expectErrorSatisfies(t -> assertThat(t).isInstanceOf(CustomException.class)
        .hasMessage(throwable.getMessage())
    )
    .verify(); //this triggers the Mono, compares the
               //signals to the expectations/assertions and wait for mono's completion

}
0 голосов
/ 02 октября 2019

Вместо использования doOnError я включил метод подписки, принимающий также потребителя ошибок:

Mono<MyCustomResponse> result = webClient.post()
            .header(HttpHeaders.CONTENT_TYPE, "application/json")
            .body(BodyInserters.fromObject(request))
            .retrieve()
            .subscribe(response -> log.info(response),
                       throwable -> throw new CustomException(throwable.getMessage()));

Эта документация очень помогает: https://projectreactor.io/docs/core/release/reference/index.html#_error_handling_operators

...