Как вызвать исключение в части ошибки реактивного вызова Spring WebClient?
Я хотел бы, чтобы следующий метод генерировал настраиваемое исключение в случае возникновения ошибки:
@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, если вызов, чтобы мой метод действительно генерировал исключение?
3 ответа
Вместо того, чтобы использовать 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
Я бы посоветовал предоставить реактивный 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
}
Метод retrieve () в WebClient выдает исключение WebClientResponseException всякий раз, когда получен ответ с кодом состояния 4xx или 5xx.
1. Вы можете настроить исключение с помощью метода onStatus ().
public Mono<JSONObject> listGithubRepositories() {
return webClient.get()
.uri(URL)
.retrieve()
.onStatus(HttpStatus::is4xxClientError, clientResponse ->
Mono.error(new MyCustomClientException())
)
.onStatus(HttpStatus::is5xxServerError, clientResponse ->
Mono.error(new MyCustomServerException())
)
.bodyToMono(JSONObject.class);
}
2. Вызвать настраиваемое исключение, проверив статус ответа.
Mono<Object> result = webClient.get().uri(URL).exchange().log().flatMap(entity -> {
HttpStatus statusCode = entity.statusCode();
if (statusCode.is4xxClientError() || statusCode.is5xxServerError())
{
return Mono.error(new Exception(statusCode.toString()));
}
return Mono.just(entity);
}).flatMap(clientResponse -> clientResponse.bodyToMono(JSONObject.class))
Ссылка: https://www.callicoder.com/spring-5-reactive-webclient-webtestclient-examples/