From 0de9172dc6035454d7b923d5cf40aef327544e83 Mon Sep 17 00:00:00 2001 From: Gerardo Roza Date: Sat, 23 Jan 2021 11:36:58 -0300 Subject: [PATCH 01/16] Moved spring-5-webclient example code to new test class, to verify everything is working as expected --- .../reactive/client/WebClientController.java | 95 --------------- .../web/client/WebClientIntegrationTest.java | 110 ++++++++++++++++++ 2 files changed, 110 insertions(+), 95 deletions(-) create mode 100644 spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java diff --git a/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/WebClientController.java b/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/WebClientController.java index 2d42e848b2..765cd561d8 100644 --- a/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/WebClientController.java +++ b/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/WebClientController.java @@ -1,36 +1,12 @@ package com.baeldung.web.reactive.client; -import java.net.URI; -import java.nio.charset.Charset; -import java.time.ZonedDateTime; -import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.TimeUnit; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ReactiveHttpOutputMessage; -import org.springframework.http.client.reactive.ClientHttpRequest; -import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.reactive.function.BodyInserter; -import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.reactive.function.client.WebClient; - -import io.netty.channel.ChannelOption; -import io.netty.handler.timeout.ReadTimeoutHandler; -import io.netty.handler.timeout.WriteTimeoutHandler; -import reactor.core.publisher.Mono; -import reactor.netty.http.client.HttpClient; @RestController public class WebClientController { @@ -42,75 +18,4 @@ public class WebClientController { response.put("field", "value"); return response; } - - public void demonstrateWebClient() { - // request - WebClient.UriSpec request1 = createWebClientWithServerURLAndDefaultValues().method(HttpMethod.POST); - WebClient.UriSpec request2 = createWebClientWithServerURLAndDefaultValues().post(); - - // request body specifications - WebClient.RequestBodySpec uri1 = createWebClientWithServerURLAndDefaultValues().method(HttpMethod.POST) - .uri("/resource"); - WebClient.RequestBodySpec uri2 = createWebClientWithServerURLAndDefaultValues().post() - .uri(URI.create("/resource")); - - // request header specification - WebClient.RequestHeadersSpec requestSpec1 = uri1.body(BodyInserters.fromPublisher(Mono.just("data"), String.class)); - WebClient.RequestHeadersSpec requestSpec2 = uri2.body(BodyInserters.fromValue("data")); - - // inserters - BodyInserter, ReactiveHttpOutputMessage> inserter1 = BodyInserters.fromPublisher(Subscriber::onComplete, String.class); - - LinkedMultiValueMap map = new LinkedMultiValueMap<>(); - map.add("key1", "value1"); - map.add("key2", "value2"); - - BodyInserter, ClientHttpRequest> inserter2 = BodyInserters.fromMultipartData(map); - BodyInserter inserter3 = BodyInserters.fromValue(new Object()); - BodyInserter inserter4 = BodyInserters.fromValue("body"); - - // responses - WebClient.ResponseSpec response1 = uri1.body(inserter3) - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML) - .acceptCharset(Charset.forName("UTF-8")) - .ifNoneMatch("*") - .ifModifiedSince(ZonedDateTime.now()) - .retrieve(); - String response2 = uri1.exchangeToMono(response -> response.bodyToMono(String.class)) - .block(); - String response3 = uri2.retrieve() - .bodyToMono(String.class) - .block(); - WebClient.ResponseSpec response4 = requestSpec2.retrieve(); - } - - private WebClient createWebClient() { - return WebClient.create(); - } - - private WebClient createWebClientWithServerURL() { - return WebClient.create("http://localhost:8081"); - } - - private WebClient createWebClientConfiguringTimeout() { - HttpClient httpClient = HttpClient.create() - .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) - .doOnConnected(conn -> conn.addHandlerLast(new ReadTimeoutHandler(5000, TimeUnit.MILLISECONDS)) - .addHandlerLast(new WriteTimeoutHandler(5000, TimeUnit.MILLISECONDS))); - - return WebClient.builder() - .clientConnector(new ReactorClientHttpConnector(httpClient)) - .build(); - } - - private WebClient createWebClientWithServerURLAndDefaultValues() { - return WebClient.builder() - .baseUrl("http://localhost:8081") - .defaultCookie("cookieKey", "cookieValue") - .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .defaultUriVariables(Collections.singletonMap("url", "http://localhost:8080")) - .build(); - } - } diff --git a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java new file mode 100644 index 0000000000..df873c618d --- /dev/null +++ b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java @@ -0,0 +1,110 @@ +package com.baeldung.web.client; + +import java.net.URI; +import java.nio.charset.Charset; +import java.time.ZonedDateTime; +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ReactiveHttpOutputMessage; +import org.springframework.http.client.reactive.ClientHttpRequest; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.reactive.function.BodyInserter; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.WebClient; + +import com.baeldung.web.reactive.client.WebClientApplication; + +import io.netty.channel.ChannelOption; +import io.netty.handler.timeout.ReadTimeoutHandler; +import io.netty.handler.timeout.WriteTimeoutHandler; +import reactor.core.publisher.Mono; +import reactor.netty.http.client.HttpClient; + +@SpringBootTest(classes = WebClientApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class WebClientIntegrationTest { + + @LocalServerPort + private int port; + + @Test + public void demonstrateWebClient() { + // request + WebClient.UriSpec request1 = createWebClientWithServerURLAndDefaultValues().method(HttpMethod.POST); + WebClient.UriSpec request2 = createWebClientWithServerURLAndDefaultValues().post(); + + // request body specifications + WebClient.RequestBodySpec uri1 = createWebClientWithServerURLAndDefaultValues().method(HttpMethod.POST) + .uri("/resource"); + WebClient.RequestBodySpec uri2 = createWebClientWithServerURLAndDefaultValues().post() + .uri(URI.create("/resource")); + + // request header specification + WebClient.RequestHeadersSpec requestSpec1 = uri1.body(BodyInserters.fromPublisher(Mono.just("data"), String.class)); + WebClient.RequestHeadersSpec requestSpec2 = uri2.body(BodyInserters.fromValue("data")); + + // inserters + BodyInserter, ReactiveHttpOutputMessage> inserter1 = BodyInserters.fromPublisher(Subscriber::onComplete, String.class); + + LinkedMultiValueMap map = new LinkedMultiValueMap<>(); + map.add("key1", "value1"); + map.add("key2", "value2"); + + BodyInserter, ClientHttpRequest> inserter2 = BodyInserters.fromMultipartData(map); + BodyInserter inserter3 = BodyInserters.fromValue(new Object()); + BodyInserter inserter4 = BodyInserters.fromValue("body"); + + // responses + WebClient.ResponseSpec response1 = uri1.body(inserter3) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML) + .acceptCharset(Charset.forName("UTF-8")) + .ifNoneMatch("*") + .ifModifiedSince(ZonedDateTime.now()) + .retrieve(); + String response2 = uri1.exchangeToMono(response -> response.bodyToMono(String.class)) + .block(); + String response3 = uri2.retrieve() + .bodyToMono(String.class) + .block(); + WebClient.ResponseSpec response4 = requestSpec2.retrieve(); + } + + private WebClient createWebClient() { + return WebClient.create(); + } + + private WebClient createWebClientWithServerURL() { + return WebClient.create("http://localhost:8081"); + } + + private WebClient createWebClientConfiguringTimeout() { + HttpClient httpClient = HttpClient.create() + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) + .doOnConnected(conn -> conn.addHandlerLast(new ReadTimeoutHandler(5000, TimeUnit.MILLISECONDS)) + .addHandlerLast(new WriteTimeoutHandler(5000, TimeUnit.MILLISECONDS))); + + return WebClient.builder() + .clientConnector(new ReactorClientHttpConnector(httpClient)) + .build(); + } + + private WebClient createWebClientWithServerURLAndDefaultValues() { + return WebClient.builder() + .baseUrl("http://localhost:8081") + .defaultCookie("cookieKey", "cookieValue") + .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .defaultUriVariables(Collections.singletonMap("url", "http://localhost:8080")) + .build(); + } +} From 3e5a1f3e50c4d18ee4e7a7be34c8aa9821aa31c8 Mon Sep 17 00:00:00 2001 From: Gerardo Roza Date: Sat, 23 Jan 2021 12:40:28 -0300 Subject: [PATCH 02/16] added endpoints to test functionality properly --- .../web/reactive/client/WebClientController.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/WebClientController.java b/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/WebClientController.java index 765cd561d8..2dfcd7533b 100644 --- a/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/WebClientController.java +++ b/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/WebClientController.java @@ -4,7 +4,11 @@ import java.util.HashMap; import java.util.Map; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; @@ -18,4 +22,14 @@ public class WebClientController { response.put("field", "value"); return response; } + + @PostMapping("/resource") + public String postResource(@RequestBody String bodyString) { + return "processed-" + bodyString; + } + + @PostMapping(value = "/resource-multipart", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public String handleFormUpload(@RequestPart("key1") String value1, @RequestPart("key2") String value2) { + return "processed-" + value1 + value2; + } } From 725465b0b6e42202a5f66498773ced8b00ec5355 Mon Sep 17 00:00:00 2001 From: Gerardo Roza Date: Sat, 23 Jan 2021 12:41:13 -0300 Subject: [PATCH 03/16] disabled security for WebClient codebase application --- .../baeldung/web/reactive/client/WebClientApplication.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/WebClientApplication.java b/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/WebClientApplication.java index f104ad30f1..aa9b81de4f 100644 --- a/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/WebClientApplication.java +++ b/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/WebClientApplication.java @@ -2,9 +2,10 @@ package com.baeldung.web.reactive.client; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; -@SpringBootApplication -public class WebClientApplication{ +@SpringBootApplication(exclude = { ReactiveSecurityAutoConfiguration.class }) +public class WebClientApplication { public static void main(String[] args) { SpringApplication.run(WebClientApplication.class, args); From 2bc7dbc70868ac34ac8d3e7edb8113338fa991d4 Mon Sep 17 00:00:00 2001 From: Gerardo Roza Date: Sun, 24 Jan 2021 15:18:29 -0300 Subject: [PATCH 04/16] added and ordered tests in WebClientIntegrationTest to match article - test not passing (reusing specs) --- .../web/client/WebClientIntegrationTest.java | 82 +++++++++++++------ 1 file changed, 58 insertions(+), 24 deletions(-) diff --git a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java index df873c618d..837ea679d2 100644 --- a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java +++ b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java @@ -1,7 +1,7 @@ package com.baeldung.web.client; import java.net.URI; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.time.ZonedDateTime; import java.util.Collections; import java.util.concurrent.TimeUnit; @@ -10,6 +10,7 @@ import org.junit.jupiter.api.Test; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.web.server.LocalServerPort; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -22,6 +23,10 @@ import org.springframework.util.MultiValueMap; import org.springframework.web.reactive.function.BodyInserter; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClient.RequestBodySpec; +import org.springframework.web.reactive.function.client.WebClient.RequestHeadersSpec; +import org.springframework.web.reactive.function.client.WebClient.ResponseSpec; +import org.springframework.web.reactive.function.client.WebClient.UriSpec; import com.baeldung.web.reactive.client.WebClientApplication; @@ -31,7 +36,7 @@ import io.netty.handler.timeout.WriteTimeoutHandler; import reactor.core.publisher.Mono; import reactor.netty.http.client.HttpClient; -@SpringBootTest(classes = WebClientApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@SpringBootTest(classes = WebClientApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT) public class WebClientIntegrationTest { @LocalServerPort @@ -40,44 +45,73 @@ public class WebClientIntegrationTest { @Test public void demonstrateWebClient() { // request - WebClient.UriSpec request1 = createWebClientWithServerURLAndDefaultValues().method(HttpMethod.POST); - WebClient.UriSpec request2 = createWebClientWithServerURLAndDefaultValues().post(); + UriSpec requestPost1 = createWebClientWithServerURLAndDefaultValues().method(HttpMethod.POST); + UriSpec requestPost2 = createWebClientWithServerURLAndDefaultValues().post(); + UriSpec requestGet = createWebClientWithServerURLAndDefaultValues().get(); // request body specifications - WebClient.RequestBodySpec uri1 = createWebClientWithServerURLAndDefaultValues().method(HttpMethod.POST) - .uri("/resource"); - WebClient.RequestBodySpec uri2 = createWebClientWithServerURLAndDefaultValues().post() - .uri(URI.create("/resource")); + RequestBodySpec bodySpecPost = requestPost1.uri("/resource"); + RequestBodySpec bodySpecPostMultipart = requestPost2.uri(uriBuilder -> uriBuilder.pathSegment("resource-multipart") + .build()); + RequestBodySpec bodySpecOverridenBaseUri = requestPost2.uri(URI.create("/resource")); // request header specification - WebClient.RequestHeadersSpec requestSpec1 = uri1.body(BodyInserters.fromPublisher(Mono.just("data"), String.class)); - WebClient.RequestHeadersSpec requestSpec2 = uri2.body(BodyInserters.fromValue("data")); + String bodyValue = "bodyValue"; + RequestHeadersSpec headerSpecPost1 = bodySpecPost.body(BodyInserters.fromPublisher(Mono.just(bodyValue), String.class)); + RequestHeadersSpec headerSpecPost2 = bodySpecPost.body(BodyInserters.fromValue(bodyValue)); + RequestHeadersSpec headerSpecGet = requestGet.uri("/resource"); - // inserters - BodyInserter, ReactiveHttpOutputMessage> inserter1 = BodyInserters.fromPublisher(Subscriber::onComplete, String.class); + // request header specification using inserters + BodyInserter, ReactiveHttpOutputMessage> inserterCompleteSuscriber = BodyInserters.fromPublisher(Subscriber::onComplete, String.class); LinkedMultiValueMap map = new LinkedMultiValueMap<>(); - map.add("key1", "value1"); - map.add("key2", "value2"); + map.add("key1", "multipartValue1"); + map.add("key2", "multipartValue2"); - BodyInserter, ClientHttpRequest> inserter2 = BodyInserters.fromMultipartData(map); - BodyInserter inserter3 = BodyInserters.fromValue(new Object()); - BodyInserter inserter4 = BodyInserters.fromValue("body"); + BodyInserter, ClientHttpRequest> inserterMultipart = BodyInserters.fromMultipartData(map); + BodyInserter inserterObject = BodyInserters.fromValue(new Object()); + BodyInserter inserterString = BodyInserters.fromValue(bodyValue); + + RequestHeadersSpec headerSpecInserterCompleteSuscriber = bodySpecPost.body(inserterCompleteSuscriber); + RequestHeadersSpec headerSpecInserterMultipart = bodySpecPostMultipart.body(inserterMultipart); + RequestHeadersSpec headerSpecInserterObject = bodySpecPost.body(inserterObject); + RequestHeadersSpec headerSpecInserterString = bodySpecPost.body(inserterString); // responses - WebClient.ResponseSpec response1 = uri1.body(inserter3) - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + ResponseSpec responsePostObject = headerSpecInserterObject.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML) - .acceptCharset(Charset.forName("UTF-8")) + .acceptCharset(StandardCharsets.UTF_8) .ifNoneMatch("*") .ifModifiedSince(ZonedDateTime.now()) .retrieve(); - String response2 = uri1.exchangeToMono(response -> response.bodyToMono(String.class)) + String responsePostString = headerSpecInserterString.exchangeToMono(response -> response.bodyToMono(String.class)) .block(); - String response3 = uri2.retrieve() + String responsePostMultipart = headerSpecInserterMultipart.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) + .retrieve() .bodyToMono(String.class) .block(); - WebClient.ResponseSpec response4 = requestSpec2.retrieve(); + String responsePostCompleteSubsciber = headerSpecInserterCompleteSuscriber.retrieve() + .bodyToMono(String.class) + .block(); + String responsePostWithBody1 = headerSpecPost1.retrieve() + .bodyToMono(String.class) + .block(); + String responsePostWithBody3 = headerSpecPost2.retrieve() + .bodyToMono(String.class) + .block(); + String responseGet = headerSpecGet.retrieve() + .bodyToMono(String.class) + .block(); + String responsePostWithNoBody = bodySpecPost.retrieve() + .bodyToMono(String.class) + .block(); + try { + bodySpecOverridenBaseUri.retrieve() + .bodyToMono(String.class) + .block(); + } catch (Exception ex) { + System.out.println(ex.getClass()); + } } private WebClient createWebClient() { @@ -101,7 +135,7 @@ public class WebClientIntegrationTest { private WebClient createWebClientWithServerURLAndDefaultValues() { return WebClient.builder() - .baseUrl("http://localhost:8081") + .baseUrl("http://localhost:" + port) .defaultCookie("cookieKey", "cookieValue") .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .defaultUriVariables(Collections.singletonMap("url", "http://localhost:8080")) From 84737e1056097486bdce4f7b6bc65cfafd930065 Mon Sep 17 00:00:00 2001 From: Gerardo Roza Date: Sun, 24 Jan 2021 17:41:34 -0300 Subject: [PATCH 05/16] added Foo for post Object scenario, removed Subscriber::onComplete scenario, fixed some other errors in examples --- .../com/baeldung/web/reactive/client/Foo.java | 20 +++ .../reactive/client/WebClientController.java | 7 +- .../web/client/WebClientIntegrationTest.java | 141 +++++++++++------- 3 files changed, 116 insertions(+), 52 deletions(-) create mode 100644 spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/Foo.java diff --git a/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/Foo.java b/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/Foo.java new file mode 100644 index 0000000000..1fc4834cfa --- /dev/null +++ b/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/Foo.java @@ -0,0 +1,20 @@ +package com.baeldung.web.reactive.client; + +public class Foo { + + private String name; + + public Foo(String name) { + super(); + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/WebClientController.java b/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/WebClientController.java index 2dfcd7533b..4c7941ac35 100644 --- a/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/WebClientController.java +++ b/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/WebClientController.java @@ -28,8 +28,13 @@ public class WebClientController { return "processed-" + bodyString; } + @PostMapping("/resource-foo") + public String postResource(@RequestBody Foo bodyFoo) { + return "processedFoo-" + bodyFoo.getName(); + } + @PostMapping(value = "/resource-multipart", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public String handleFormUpload(@RequestPart("key1") String value1, @RequestPart("key2") String value2) { - return "processed-" + value1 + value2; + return "processed-" + value1 + "-" + value2; } } diff --git a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java index 837ea679d2..b49aff9575 100644 --- a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java +++ b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java @@ -1,9 +1,13 @@ package com.baeldung.web.client; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + import java.net.URI; import java.nio.charset.StandardCharsets; import java.time.ZonedDateTime; import java.util.Collections; +import java.util.Map; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; @@ -12,6 +16,7 @@ import org.reactivestreams.Subscriber; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; @@ -24,13 +29,17 @@ import org.springframework.web.reactive.function.BodyInserter; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient.RequestBodySpec; +import org.springframework.web.reactive.function.client.WebClient.RequestBodyUriSpec; import org.springframework.web.reactive.function.client.WebClient.RequestHeadersSpec; import org.springframework.web.reactive.function.client.WebClient.ResponseSpec; import org.springframework.web.reactive.function.client.WebClient.UriSpec; +import org.springframework.web.reactive.function.client.WebClientRequestException; +import com.baeldung.web.reactive.client.Foo; import com.baeldung.web.reactive.client.WebClientApplication; import io.netty.channel.ChannelOption; +import io.netty.channel.ConnectTimeoutException; import io.netty.handler.timeout.ReadTimeoutHandler; import io.netty.handler.timeout.WriteTimeoutHandler; import reactor.core.publisher.Mono; @@ -43,27 +52,37 @@ public class WebClientIntegrationTest { private int port; @Test - public void demonstrateWebClient() { - // request - UriSpec requestPost1 = createWebClientWithServerURLAndDefaultValues().method(HttpMethod.POST); - UriSpec requestPost2 = createWebClientWithServerURLAndDefaultValues().post(); - UriSpec requestGet = createWebClientWithServerURLAndDefaultValues().get(); + public void givenDifferentScenarios_whenRequestsSent_thenObtainExpectedResponses() { + // WebClient + WebClient client1 = WebClient.create(); + WebClient client2 = WebClient.create("http://localhost:" + port); + WebClient client3 = WebClient.builder() + .baseUrl("http://localhost:" + port) + .defaultCookie("cookieKey", "cookieValue") + .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .defaultUriVariables(Collections.singletonMap("url", "http://localhost:8080")) + .build(); + + // request specification + UriSpec uriSpecPost1 = client1.method(HttpMethod.POST); + UriSpec uriSpecPost2 = client2.post(); + UriSpec requestGet = client3.get(); + + // uri specification + RequestBodySpec bodySpecPost = uriSpecPost1.uri("http://localhost:" + port + "/resource"); + RequestBodySpec bodySpecPostMultipart = uriSpecPost2.uri(uriBuilder -> uriBuilder.pathSegment("resource-multipart") + .build()); + RequestBodySpec bodySpecOverridenBaseUri = createDefaultPostRequest().uri(URI.create("/resource")); + RequestBodySpec fooBodySpecPost = createDefaultPostRequest().uri(URI.create("/resource-foo")); // request body specifications - RequestBodySpec bodySpecPost = requestPost1.uri("/resource"); - RequestBodySpec bodySpecPostMultipart = requestPost2.uri(uriBuilder -> uriBuilder.pathSegment("resource-multipart") - .build()); - RequestBodySpec bodySpecOverridenBaseUri = requestPost2.uri(URI.create("/resource")); - - // request header specification String bodyValue = "bodyValue"; RequestHeadersSpec headerSpecPost1 = bodySpecPost.body(BodyInserters.fromPublisher(Mono.just(bodyValue), String.class)); - RequestHeadersSpec headerSpecPost2 = bodySpecPost.body(BodyInserters.fromValue(bodyValue)); + RequestHeadersSpec headerSpecPost2 = createDefaultPostResourceRequest().body(BodyInserters.fromValue(bodyValue)); + RequestHeadersSpec headerSpecFooPost = fooBodySpecPost.body(BodyInserters.fromValue(new Foo("fooName"))); RequestHeadersSpec headerSpecGet = requestGet.uri("/resource"); - // request header specification using inserters - BodyInserter, ReactiveHttpOutputMessage> inserterCompleteSuscriber = BodyInserters.fromPublisher(Subscriber::onComplete, String.class); - + // request body specifications - using inserters LinkedMultiValueMap map = new LinkedMultiValueMap<>(); map.add("key1", "multipartValue1"); map.add("key2", "multipartValue2"); @@ -72,73 +91,93 @@ public class WebClientIntegrationTest { BodyInserter inserterObject = BodyInserters.fromValue(new Object()); BodyInserter inserterString = BodyInserters.fromValue(bodyValue); - RequestHeadersSpec headerSpecInserterCompleteSuscriber = bodySpecPost.body(inserterCompleteSuscriber); RequestHeadersSpec headerSpecInserterMultipart = bodySpecPostMultipart.body(inserterMultipart); - RequestHeadersSpec headerSpecInserterObject = bodySpecPost.body(inserterObject); - RequestHeadersSpec headerSpecInserterString = bodySpecPost.body(inserterString); + RequestHeadersSpec headerSpecInserterObject = createDefaultPostResourceRequest().body(inserterObject); + RequestHeadersSpec headerSpecInserterString = createDefaultPostResourceRequest().body(inserterString); - // responses - ResponseSpec responsePostObject = headerSpecInserterObject.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + // request header specification + RequestHeadersSpec headerSpecInserterStringWithHeaders = headerSpecInserterString.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML) .acceptCharset(StandardCharsets.UTF_8) .ifNoneMatch("*") - .ifModifiedSince(ZonedDateTime.now()) - .retrieve(); - String responsePostString = headerSpecInserterString.exchangeToMono(response -> response.bodyToMono(String.class)) + .ifModifiedSince(ZonedDateTime.now()); + + // request + ResponseSpec responseSpecPostString = headerSpecInserterStringWithHeaders.retrieve(); + String responsePostString = responseSpecPostString.bodyToMono(String.class) .block(); String responsePostMultipart = headerSpecInserterMultipart.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) .retrieve() .bodyToMono(String.class) .block(); - String responsePostCompleteSubsciber = headerSpecInserterCompleteSuscriber.retrieve() - .bodyToMono(String.class) - .block(); String responsePostWithBody1 = headerSpecPost1.retrieve() .bodyToMono(String.class) .block(); String responsePostWithBody3 = headerSpecPost2.retrieve() .bodyToMono(String.class) .block(); - String responseGet = headerSpecGet.retrieve() + String responsePostFoo = headerSpecFooPost.retrieve() .bodyToMono(String.class) .block(); + ParameterizedTypeReference> ref = new ParameterizedTypeReference>() { + }; + Map responseGet = headerSpecGet.retrieve() + .bodyToMono(ref) + .block(); String responsePostWithNoBody = bodySpecPost.retrieve() .bodyToMono(String.class) .block(); - try { + + // response assertions + assertThat(responsePostString).isEqualTo("processed-bodyValue"); + assertThat(responsePostMultipart).isEqualTo("processed-multipartValue1-multipartValue2"); + assertThat(responsePostWithBody1).isEqualTo("processed-"); + assertThat(responsePostWithBody3).isEqualTo("processed-"); + assertThat(responseGet).containsEntry("field", "value"); + assertThat(responsePostWithNoBody).isEqualTo("processed-"); + assertThat(responsePostFoo).isEqualTo("processed-fooName"); + + assertThrows(WebClientRequestException.class, () -> { + String responsePostObject = headerSpecInserterObject.exchangeToMono(response -> response.bodyToMono(String.class)) + .block(); + }); + assertThrows(WebClientRequestException.class, () -> { bodySpecOverridenBaseUri.retrieve() .bodyToMono(String.class) .block(); - } catch (Exception ex) { - System.out.println(ex.getClass()); - } + }); + } - private WebClient createWebClient() { - return WebClient.create(); - } - - private WebClient createWebClientWithServerURL() { - return WebClient.create("http://localhost:8081"); - } - - private WebClient createWebClientConfiguringTimeout() { + @Test + public void givenWebClientWithTimeoutConfigurations_whenRequestUsingWronglyConfiguredPublisher_thenObtainTimeout() { HttpClient httpClient = HttpClient.create() - .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) - .doOnConnected(conn -> conn.addHandlerLast(new ReadTimeoutHandler(5000, TimeUnit.MILLISECONDS)) - .addHandlerLast(new WriteTimeoutHandler(5000, TimeUnit.MILLISECONDS))); + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000) + .doOnConnected(conn -> conn.addHandlerLast(new ReadTimeoutHandler(1000, TimeUnit.MILLISECONDS)) + .addHandlerLast(new WriteTimeoutHandler(1000, TimeUnit.MILLISECONDS))); - return WebClient.builder() + WebClient timeoutClient = WebClient.builder() .clientConnector(new ReactorClientHttpConnector(httpClient)) .build(); + + BodyInserter, ReactiveHttpOutputMessage> inserterCompleteSuscriber = BodyInserters.fromPublisher(Subscriber::onComplete, String.class); + RequestHeadersSpec headerSpecInserterCompleteSuscriber = timeoutClient.post() + .uri("/resource") + .body(inserterCompleteSuscriber); + WebClientRequestException exception = assertThrows(WebClientRequestException.class, () -> { + headerSpecInserterCompleteSuscriber.retrieve() + .bodyToMono(String.class) + .block(); + }); + assertThat(exception.getCause()).isInstanceOf(ConnectTimeoutException.class); } - private WebClient createWebClientWithServerURLAndDefaultValues() { - return WebClient.builder() - .baseUrl("http://localhost:" + port) - .defaultCookie("cookieKey", "cookieValue") - .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .defaultUriVariables(Collections.singletonMap("url", "http://localhost:8080")) - .build(); + private RequestBodyUriSpec createDefaultPostRequest() { + return WebClient.create("http://localhost:" + port) + .post(); + } + + private RequestBodySpec createDefaultPostResourceRequest() { + return createDefaultPostRequest().uri("/resource"); } } From cfe47f772a434eb3b1c462144c9a2301e3418cf4 Mon Sep 17 00:00:00 2001 From: Gerardo Roza Date: Mon, 25 Jan 2021 11:37:12 -0300 Subject: [PATCH 06/16] fixed rest of tests --- .../com/baeldung/web/reactive/client/Foo.java | 4 ++++ .../reactive/client/WebClientController.java | 10 ++++---- .../web/client/WebClientIntegrationTest.java | 24 ++++++++++++------- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/Foo.java b/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/Foo.java index 1fc4834cfa..c6e3678832 100644 --- a/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/Foo.java +++ b/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/Foo.java @@ -4,6 +4,10 @@ public class Foo { private String name; + public Foo() { + super(); + } + public Foo(String name) { super(); this.name = name; diff --git a/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/WebClientController.java b/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/WebClientController.java index 4c7941ac35..1a91001807 100644 --- a/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/WebClientController.java +++ b/spring-5-reactive/src/main/java/com/baeldung/web/reactive/client/WebClientController.java @@ -12,6 +12,8 @@ import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; + @RestController public class WebClientController { @@ -24,13 +26,13 @@ public class WebClientController { } @PostMapping("/resource") - public String postResource(@RequestBody String bodyString) { - return "processed-" + bodyString; + public Mono postStringResource(@RequestBody Mono bodyString) { + return bodyString.map(body -> "processed-" + body); } @PostMapping("/resource-foo") - public String postResource(@RequestBody Foo bodyFoo) { - return "processedFoo-" + bodyFoo.getName(); + public Mono postFooResource(@RequestBody Mono bodyFoo) { + return bodyFoo.map(foo -> "processedFoo-" + foo.getName()); } @PostMapping(value = "/resource-multipart", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) diff --git a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java index b49aff9575..04a4031c62 100644 --- a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java +++ b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java @@ -17,8 +17,10 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.web.server.LocalServerPort; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.core.codec.CodecException; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ReactiveHttpOutputMessage; import org.springframework.http.client.reactive.ClientHttpRequest; @@ -72,8 +74,8 @@ public class WebClientIntegrationTest { RequestBodySpec bodySpecPost = uriSpecPost1.uri("http://localhost:" + port + "/resource"); RequestBodySpec bodySpecPostMultipart = uriSpecPost2.uri(uriBuilder -> uriBuilder.pathSegment("resource-multipart") .build()); + RequestBodySpec fooBodySpecPost = createDefaultPostRequest().uri("/resource-foo"); RequestBodySpec bodySpecOverridenBaseUri = createDefaultPostRequest().uri(URI.create("/resource")); - RequestBodySpec fooBodySpecPost = createDefaultPostRequest().uri(URI.create("/resource-foo")); // request body specifications String bodyValue = "bodyValue"; @@ -124,23 +126,27 @@ public class WebClientIntegrationTest { Map responseGet = headerSpecGet.retrieve() .bodyToMono(ref) .block(); - String responsePostWithNoBody = bodySpecPost.retrieve() - .bodyToMono(String.class) + Map responsePostWithNoBody = createDefaultPostResourceRequest().exchangeToMono(responseHandler -> { + assertThat(responseHandler.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + return responseHandler.bodyToMono(ref); + }) .block(); // response assertions assertThat(responsePostString).isEqualTo("processed-bodyValue"); assertThat(responsePostMultipart).isEqualTo("processed-multipartValue1-multipartValue2"); - assertThat(responsePostWithBody1).isEqualTo("processed-"); - assertThat(responsePostWithBody3).isEqualTo("processed-"); + assertThat(responsePostWithBody1).isEqualTo("processed-bodyValue"); + assertThat(responsePostWithBody3).isEqualTo("processed-bodyValue"); assertThat(responseGet).containsEntry("field", "value"); - assertThat(responsePostWithNoBody).isEqualTo("processed-"); - assertThat(responsePostFoo).isEqualTo("processed-fooName"); + assertThat(responsePostFoo).isEqualTo("processedFoo-fooName"); + assertThat(responsePostWithNoBody).containsEntry("error", "Bad Request"); - assertThrows(WebClientRequestException.class, () -> { - String responsePostObject = headerSpecInserterObject.exchangeToMono(response -> response.bodyToMono(String.class)) + // assert sending plain `new Object()` as request body + assertThrows(CodecException.class, () -> { + headerSpecInserterObject.exchangeToMono(response -> response.bodyToMono(String.class)) .block(); }); + // assert sending request overriding base uri assertThrows(WebClientRequestException.class, () -> { bodySpecOverridenBaseUri.retrieve() .bodyToMono(String.class) From 0cda08a95e6326c4e5dfec76c54e2a865974b1d5 Mon Sep 17 00:00:00 2001 From: Gerardo Roza Date: Mon, 25 Jan 2021 11:41:57 -0300 Subject: [PATCH 07/16] replaced existing scenario to use an alternative body method --- .../java/com/baeldung/web/client/WebClientIntegrationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java index 04a4031c62..34627b00ed 100644 --- a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java +++ b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java @@ -81,7 +81,7 @@ public class WebClientIntegrationTest { String bodyValue = "bodyValue"; RequestHeadersSpec headerSpecPost1 = bodySpecPost.body(BodyInserters.fromPublisher(Mono.just(bodyValue), String.class)); RequestHeadersSpec headerSpecPost2 = createDefaultPostResourceRequest().body(BodyInserters.fromValue(bodyValue)); - RequestHeadersSpec headerSpecFooPost = fooBodySpecPost.body(BodyInserters.fromValue(new Foo("fooName"))); + RequestHeadersSpec headerSpecFooPost = fooBodySpecPost.body(Mono.just(new Foo("fooName")), Foo.class); RequestHeadersSpec headerSpecGet = requestGet.uri("/resource"); // request body specifications - using inserters From a37812ce04cb20c7a1a76deabbe15137c6fb5c26 Mon Sep 17 00:00:00 2001 From: Gerardo Roza Date: Mon, 25 Jan 2021 11:44:44 -0300 Subject: [PATCH 08/16] added additional scenario using bodyValue method --- .../com/baeldung/web/client/WebClientIntegrationTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java index 34627b00ed..032d62b04d 100644 --- a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java +++ b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java @@ -81,6 +81,7 @@ public class WebClientIntegrationTest { String bodyValue = "bodyValue"; RequestHeadersSpec headerSpecPost1 = bodySpecPost.body(BodyInserters.fromPublisher(Mono.just(bodyValue), String.class)); RequestHeadersSpec headerSpecPost2 = createDefaultPostResourceRequest().body(BodyInserters.fromValue(bodyValue)); + RequestHeadersSpec headerSpecPost3 = createDefaultPostResourceRequest().bodyValue(bodyValue); RequestHeadersSpec headerSpecFooPost = fooBodySpecPost.body(Mono.just(new Foo("fooName")), Foo.class); RequestHeadersSpec headerSpecGet = requestGet.uri("/resource"); @@ -115,6 +116,9 @@ public class WebClientIntegrationTest { String responsePostWithBody1 = headerSpecPost1.retrieve() .bodyToMono(String.class) .block(); + String responsePostWithBody2 = headerSpecPost2.retrieve() + .bodyToMono(String.class) + .block(); String responsePostWithBody3 = headerSpecPost2.retrieve() .bodyToMono(String.class) .block(); @@ -136,6 +140,7 @@ public class WebClientIntegrationTest { assertThat(responsePostString).isEqualTo("processed-bodyValue"); assertThat(responsePostMultipart).isEqualTo("processed-multipartValue1-multipartValue2"); assertThat(responsePostWithBody1).isEqualTo("processed-bodyValue"); + assertThat(responsePostWithBody2).isEqualTo("processed-bodyValue"); assertThat(responsePostWithBody3).isEqualTo("processed-bodyValue"); assertThat(responseGet).containsEntry("field", "value"); assertThat(responsePostFoo).isEqualTo("processedFoo-fooName"); From 615322468ecf4093d2e96c453b6c389dac7e9e5c Mon Sep 17 00:00:00 2001 From: Gerardo Roza Date: Mon, 25 Jan 2021 12:26:37 -0300 Subject: [PATCH 09/16] made tests assertions non-blocking --- spring-5-reactive/pom.xml | 11 ++- .../web/client/WebClientIntegrationTest.java | 68 ++++++++++--------- 2 files changed, 45 insertions(+), 34 deletions(-) diff --git a/spring-5-reactive/pom.xml b/spring-5-reactive/pom.xml index 60c8b90e16..40791faaaf 100644 --- a/spring-5-reactive/pom.xml +++ b/spring-5-reactive/pom.xml @@ -1,6 +1,7 @@ - + 4.0.0 spring-5-reactive 0.0.1-SNAPSHOT @@ -73,6 +74,12 @@ spring-security-test test + + io.projectreactor + reactor-test + test + + diff --git a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java index 032d62b04d..01de550a9c 100644 --- a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java +++ b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java @@ -46,6 +46,7 @@ import io.netty.handler.timeout.ReadTimeoutHandler; import io.netty.handler.timeout.WriteTimeoutHandler; import reactor.core.publisher.Mono; import reactor.netty.http.client.HttpClient; +import reactor.test.StepVerifier; @SpringBootTest(classes = WebClientApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT) public class WebClientIntegrationTest { @@ -107,44 +108,46 @@ public class WebClientIntegrationTest { // request ResponseSpec responseSpecPostString = headerSpecInserterStringWithHeaders.retrieve(); - String responsePostString = responseSpecPostString.bodyToMono(String.class) - .block(); - String responsePostMultipart = headerSpecInserterMultipart.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) + Mono responsePostString = responseSpecPostString.bodyToMono(String.class); + Mono responsePostMultipart = headerSpecInserterMultipart.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) .retrieve() - .bodyToMono(String.class) - .block(); - String responsePostWithBody1 = headerSpecPost1.retrieve() - .bodyToMono(String.class) - .block(); - String responsePostWithBody2 = headerSpecPost2.retrieve() - .bodyToMono(String.class) - .block(); - String responsePostWithBody3 = headerSpecPost2.retrieve() - .bodyToMono(String.class) - .block(); - String responsePostFoo = headerSpecFooPost.retrieve() - .bodyToMono(String.class) - .block(); + .bodyToMono(String.class); + Mono responsePostWithBody1 = headerSpecPost1.retrieve() + .bodyToMono(String.class); + Mono responsePostWithBody2 = headerSpecPost2.retrieve() + .bodyToMono(String.class); + Mono responsePostWithBody3 = headerSpecPost2.retrieve() + .bodyToMono(String.class); + Mono responsePostFoo = headerSpecFooPost.retrieve() + .bodyToMono(String.class); ParameterizedTypeReference> ref = new ParameterizedTypeReference>() { }; - Map responseGet = headerSpecGet.retrieve() - .bodyToMono(ref) - .block(); - Map responsePostWithNoBody = createDefaultPostResourceRequest().exchangeToMono(responseHandler -> { + Mono> responseGet = headerSpecGet.retrieve() + .bodyToMono(ref); + Mono> responsePostWithNoBody = createDefaultPostResourceRequest().exchangeToMono(responseHandler -> { assertThat(responseHandler.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST); return responseHandler.bodyToMono(ref); - }) - .block(); + }); // response assertions - assertThat(responsePostString).isEqualTo("processed-bodyValue"); - assertThat(responsePostMultipart).isEqualTo("processed-multipartValue1-multipartValue2"); - assertThat(responsePostWithBody1).isEqualTo("processed-bodyValue"); - assertThat(responsePostWithBody2).isEqualTo("processed-bodyValue"); - assertThat(responsePostWithBody3).isEqualTo("processed-bodyValue"); - assertThat(responseGet).containsEntry("field", "value"); - assertThat(responsePostFoo).isEqualTo("processedFoo-fooName"); - assertThat(responsePostWithNoBody).containsEntry("error", "Bad Request"); + StepVerifier.create(responsePostString) + .expectNext("processed-bodyValue"); + StepVerifier.create(responsePostMultipart) + .expectNext("processed-multipartValue1-multipartValue2"); + StepVerifier.create(responsePostWithBody1) + .expectNext("processed-bodyValue"); + StepVerifier.create(responsePostWithBody2) + .expectNext("processed-bodyValue"); + StepVerifier.create(responsePostWithBody3) + .expectNext("processed-bodyValue"); + StepVerifier.create(responseGet) + .expectNextMatches(nextMap -> nextMap.get("field") + .equals("value")); + StepVerifier.create(responsePostFoo) + .expectNext("processedFoo-fooName"); + StepVerifier.create(responsePostWithNoBody) + .expectNextMatches(nextMap -> nextMap.get("error") + .equals("Bad Request")); // assert sending plain `new Object()` as request body assertThrows(CodecException.class, () -> { @@ -152,11 +155,12 @@ public class WebClientIntegrationTest { .block(); }); // assert sending request overriding base uri - assertThrows(WebClientRequestException.class, () -> { + Exception exception = assertThrows(WebClientRequestException.class, () -> { bodySpecOverridenBaseUri.retrieve() .bodyToMono(String.class) .block(); }); + assertThat(exception.getMessage()).contains("Connection refused"); } From 384b89d3ea78c39568fadf8e6a051f670952e398 Mon Sep 17 00:00:00 2001 From: Gerardo Roza Date: Tue, 26 Jan 2021 12:57:49 -0300 Subject: [PATCH 10/16] added response timeout to timeout configuration --- .../com/baeldung/web/client/WebClientIntegrationTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java index 01de550a9c..f54d8a6c3d 100644 --- a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java +++ b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import java.net.URI; import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.time.ZonedDateTime; import java.util.Collections; import java.util.Map; @@ -116,7 +117,7 @@ public class WebClientIntegrationTest { .bodyToMono(String.class); Mono responsePostWithBody2 = headerSpecPost2.retrieve() .bodyToMono(String.class); - Mono responsePostWithBody3 = headerSpecPost2.retrieve() + Mono responsePostWithBody3 = headerSpecPost3.retrieve() .bodyToMono(String.class); Mono responsePostFoo = headerSpecFooPost.retrieve() .bodyToMono(String.class); @@ -168,6 +169,7 @@ public class WebClientIntegrationTest { public void givenWebClientWithTimeoutConfigurations_whenRequestUsingWronglyConfiguredPublisher_thenObtainTimeout() { HttpClient httpClient = HttpClient.create() .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000) + .responseTimeout(Duration.ofMillis(1000)) .doOnConnected(conn -> conn.addHandlerLast(new ReadTimeoutHandler(1000, TimeUnit.MILLISECONDS)) .addHandlerLast(new WriteTimeoutHandler(1000, TimeUnit.MILLISECONDS))); From f8ca77bc71323ac704fb138db497e05b4bd11562 Mon Sep 17 00:00:00 2001 From: Gerardo Roza Date: Tue, 26 Jan 2021 16:05:31 -0300 Subject: [PATCH 11/16] fixed and improved reactive tests not getting verified --- .../web/client/WebClientIntegrationTest.java | 60 ++++++++++--------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java index f54d8a6c3d..cbf11766e1 100644 --- a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java +++ b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java @@ -1,7 +1,6 @@ package com.baeldung.web.client; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; import java.net.URI; import java.nio.charset.StandardCharsets; @@ -42,7 +41,6 @@ import com.baeldung.web.reactive.client.Foo; import com.baeldung.web.reactive.client.WebClientApplication; import io.netty.channel.ChannelOption; -import io.netty.channel.ConnectTimeoutException; import io.netty.handler.timeout.ReadTimeoutHandler; import io.netty.handler.timeout.WriteTimeoutHandler; import reactor.core.publisher.Mono; @@ -129,40 +127,45 @@ public class WebClientIntegrationTest { assertThat(responseHandler.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST); return responseHandler.bodyToMono(ref); }); + Mono responsePostOverridenBaseUri = bodySpecOverridenBaseUri.retrieve() + .bodyToMono(String.class); // response assertions StepVerifier.create(responsePostString) - .expectNext("processed-bodyValue"); + .expectNext("processed-bodyValue") + .verifyComplete(); StepVerifier.create(responsePostMultipart) - .expectNext("processed-multipartValue1-multipartValue2"); + .expectNext("processed-multipartValue1-multipartValue2") + .verifyComplete(); StepVerifier.create(responsePostWithBody1) - .expectNext("processed-bodyValue"); + .expectNext("processed-bodyValue") + .verifyComplete(); StepVerifier.create(responsePostWithBody2) - .expectNext("processed-bodyValue"); + .expectNext("processed-bodyValue") + .verifyComplete(); StepVerifier.create(responsePostWithBody3) - .expectNext("processed-bodyValue"); + .expectNext("processed-bodyValue") + .verifyComplete(); StepVerifier.create(responseGet) .expectNextMatches(nextMap -> nextMap.get("field") - .equals("value")); + .equals("value")) + .verifyComplete(); StepVerifier.create(responsePostFoo) - .expectNext("processedFoo-fooName"); + .expectNext("processedFoo-fooName") + .verifyComplete(); StepVerifier.create(responsePostWithNoBody) .expectNextMatches(nextMap -> nextMap.get("error") - .equals("Bad Request")); - - // assert sending plain `new Object()` as request body - assertThrows(CodecException.class, () -> { - headerSpecInserterObject.exchangeToMono(response -> response.bodyToMono(String.class)) - .block(); - }); + .equals("Bad Request")) + .verifyComplete(); // assert sending request overriding base uri - Exception exception = assertThrows(WebClientRequestException.class, () -> { - bodySpecOverridenBaseUri.retrieve() - .bodyToMono(String.class) - .block(); - }); - assertThat(exception.getMessage()).contains("Connection refused"); - + StepVerifier.create(responsePostOverridenBaseUri) + .expectErrorMatches(ex -> WebClientRequestException.class.isAssignableFrom(ex.getClass()) && ex.getMessage() + .contains("Connection refused")) + .verify(); + // assert error plain `new Object()` as request body + StepVerifier.create(headerSpecInserterObject.exchangeToMono(response -> response.bodyToMono(String.class))) + .expectError(CodecException.class) + .verify(); } @Test @@ -181,12 +184,11 @@ public class WebClientIntegrationTest { RequestHeadersSpec headerSpecInserterCompleteSuscriber = timeoutClient.post() .uri("/resource") .body(inserterCompleteSuscriber); - WebClientRequestException exception = assertThrows(WebClientRequestException.class, () -> { - headerSpecInserterCompleteSuscriber.retrieve() - .bodyToMono(String.class) - .block(); - }); - assertThat(exception.getCause()).isInstanceOf(ConnectTimeoutException.class); + + StepVerifier.create(headerSpecInserterCompleteSuscriber.retrieve() + .bodyToMono(String.class)) + .expectTimeout(Duration.ofMillis(2000)) + .verify(); } private RequestBodyUriSpec createDefaultPostRequest() { From d87cd065aa9ecb87dd080cce8b9b4dc14203a341 Mon Sep 17 00:00:00 2001 From: Gerardo Roza Date: Tue, 26 Jan 2021 16:31:14 -0300 Subject: [PATCH 12/16] cleaned WebClient. definition for spec, for consistency and simplicity --- .../com/baeldung/web/client/WebClientIntegrationTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java index cbf11766e1..c8a306c4bc 100644 --- a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java +++ b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java @@ -66,8 +66,8 @@ public class WebClientIntegrationTest { .build(); // request specification - UriSpec uriSpecPost1 = client1.method(HttpMethod.POST); - UriSpec uriSpecPost2 = client2.post(); + UriSpec uriSpecPost1 = client1.method(HttpMethod.POST); + UriSpec uriSpecPost2 = client2.post(); UriSpec requestGet = client3.get(); // uri specification From 32f35285754fc004ad48e08a645dc2759b0b851a Mon Sep 17 00:00:00 2001 From: Gerardo Roza Date: Tue, 26 Jan 2021 17:41:44 -0300 Subject: [PATCH 13/16] added more common exchangeToMono example for article --- .../web/client/WebClientIntegrationTest.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java index c8a306c4bc..9116ff3e63 100644 --- a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java +++ b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java @@ -115,8 +115,18 @@ public class WebClientIntegrationTest { .bodyToMono(String.class); Mono responsePostWithBody2 = headerSpecPost2.retrieve() .bodyToMono(String.class); - Mono responsePostWithBody3 = headerSpecPost3.retrieve() - .bodyToMono(String.class); + Mono responsePostWithBody3 = headerSpecPost3.exchangeToMono(response -> { + if (response.statusCode() + .equals(HttpStatus.OK)) { + return response.bodyToMono(String.class); + } else if (response.statusCode() + .is4xxClientError()) { + return Mono.just("Error response"); + } else { + return response.createException() + .flatMap(Mono::error); + } + }); Mono responsePostFoo = headerSpecFooPost.retrieve() .bodyToMono(String.class); ParameterizedTypeReference> ref = new ParameterizedTypeReference>() { From 0f668a63b91dadf4a9e5dc479a38f1d8c954a33b Mon Sep 17 00:00:00 2001 From: Gerardo Roza Date: Tue, 26 Jan 2021 17:53:48 -0300 Subject: [PATCH 14/16] renamed headersSpec to match article --- .../web/client/WebClientIntegrationTest.java | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java index 9116ff3e63..49edf422b0 100644 --- a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java +++ b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java @@ -79,11 +79,11 @@ public class WebClientIntegrationTest { // request body specifications String bodyValue = "bodyValue"; - RequestHeadersSpec headerSpecPost1 = bodySpecPost.body(BodyInserters.fromPublisher(Mono.just(bodyValue), String.class)); - RequestHeadersSpec headerSpecPost2 = createDefaultPostResourceRequest().body(BodyInserters.fromValue(bodyValue)); - RequestHeadersSpec headerSpecPost3 = createDefaultPostResourceRequest().bodyValue(bodyValue); - RequestHeadersSpec headerSpecFooPost = fooBodySpecPost.body(Mono.just(new Foo("fooName")), Foo.class); - RequestHeadersSpec headerSpecGet = requestGet.uri("/resource"); + RequestHeadersSpec headersSpecPost1 = bodySpecPost.body(BodyInserters.fromPublisher(Mono.just(bodyValue), String.class)); + RequestHeadersSpec headersSpecPost2 = createDefaultPostResourceRequest().body(BodyInserters.fromValue(bodyValue)); + RequestHeadersSpec headersSpecPost3 = createDefaultPostResourceRequest().bodyValue(bodyValue); + RequestHeadersSpec headersSpecFooPost = fooBodySpecPost.body(Mono.just(new Foo("fooName")), Foo.class); + RequestHeadersSpec headersSpecGet = requestGet.uri("/resource"); // request body specifications - using inserters LinkedMultiValueMap map = new LinkedMultiValueMap<>(); @@ -94,28 +94,28 @@ public class WebClientIntegrationTest { BodyInserter inserterObject = BodyInserters.fromValue(new Object()); BodyInserter inserterString = BodyInserters.fromValue(bodyValue); - RequestHeadersSpec headerSpecInserterMultipart = bodySpecPostMultipart.body(inserterMultipart); - RequestHeadersSpec headerSpecInserterObject = createDefaultPostResourceRequest().body(inserterObject); - RequestHeadersSpec headerSpecInserterString = createDefaultPostResourceRequest().body(inserterString); + RequestHeadersSpec headersSpecInserterMultipart = bodySpecPostMultipart.body(inserterMultipart); + RequestHeadersSpec headersSpecInserterObject = createDefaultPostResourceRequest().body(inserterObject); + RequestHeadersSpec headersSpecInserterString = createDefaultPostResourceRequest().body(inserterString); // request header specification - RequestHeadersSpec headerSpecInserterStringWithHeaders = headerSpecInserterString.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + RequestHeadersSpec headersSpecInserterStringWithHeaders = headersSpecInserterString.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML) .acceptCharset(StandardCharsets.UTF_8) .ifNoneMatch("*") .ifModifiedSince(ZonedDateTime.now()); // request - ResponseSpec responseSpecPostString = headerSpecInserterStringWithHeaders.retrieve(); + ResponseSpec responseSpecPostString = headersSpecInserterStringWithHeaders.retrieve(); Mono responsePostString = responseSpecPostString.bodyToMono(String.class); - Mono responsePostMultipart = headerSpecInserterMultipart.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) + Mono responsePostMultipart = headersSpecInserterMultipart.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) .retrieve() .bodyToMono(String.class); - Mono responsePostWithBody1 = headerSpecPost1.retrieve() + Mono responsePostWithBody1 = headersSpecPost1.retrieve() .bodyToMono(String.class); - Mono responsePostWithBody2 = headerSpecPost2.retrieve() + Mono responsePostWithBody2 = headersSpecPost2.retrieve() .bodyToMono(String.class); - Mono responsePostWithBody3 = headerSpecPost3.exchangeToMono(response -> { + Mono responsePostWithBody3 = headersSpecPost3.exchangeToMono(response -> { if (response.statusCode() .equals(HttpStatus.OK)) { return response.bodyToMono(String.class); @@ -127,11 +127,11 @@ public class WebClientIntegrationTest { .flatMap(Mono::error); } }); - Mono responsePostFoo = headerSpecFooPost.retrieve() + Mono responsePostFoo = headersSpecFooPost.retrieve() .bodyToMono(String.class); ParameterizedTypeReference> ref = new ParameterizedTypeReference>() { }; - Mono> responseGet = headerSpecGet.retrieve() + Mono> responseGet = headersSpecGet.retrieve() .bodyToMono(ref); Mono> responsePostWithNoBody = createDefaultPostResourceRequest().exchangeToMono(responseHandler -> { assertThat(responseHandler.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST); @@ -173,7 +173,7 @@ public class WebClientIntegrationTest { .contains("Connection refused")) .verify(); // assert error plain `new Object()` as request body - StepVerifier.create(headerSpecInserterObject.exchangeToMono(response -> response.bodyToMono(String.class))) + StepVerifier.create(headersSpecInserterObject.exchangeToMono(response -> response.bodyToMono(String.class))) .expectError(CodecException.class) .verify(); } @@ -191,11 +191,11 @@ public class WebClientIntegrationTest { .build(); BodyInserter, ReactiveHttpOutputMessage> inserterCompleteSuscriber = BodyInserters.fromPublisher(Subscriber::onComplete, String.class); - RequestHeadersSpec headerSpecInserterCompleteSuscriber = timeoutClient.post() + RequestHeadersSpec headersSpecInserterCompleteSuscriber = timeoutClient.post() .uri("/resource") .body(inserterCompleteSuscriber); - StepVerifier.create(headerSpecInserterCompleteSuscriber.retrieve() + StepVerifier.create(headersSpecInserterCompleteSuscriber.retrieve() .bodyToMono(String.class)) .expectTimeout(Duration.ofMillis(2000)) .verify(); From c477f22f27fbe4c3900fa5e5ea79223e9dc255b6 Mon Sep 17 00:00:00 2001 From: Gerardo Roza Date: Tue, 26 Jan 2021 18:17:17 -0300 Subject: [PATCH 15/16] modified url override example, to use the client3 (the one defining the baseUrl) --- .../java/com/baeldung/web/client/WebClientIntegrationTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java index 49edf422b0..eddbf74868 100644 --- a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java +++ b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java @@ -75,7 +75,8 @@ public class WebClientIntegrationTest { RequestBodySpec bodySpecPostMultipart = uriSpecPost2.uri(uriBuilder -> uriBuilder.pathSegment("resource-multipart") .build()); RequestBodySpec fooBodySpecPost = createDefaultPostRequest().uri("/resource-foo"); - RequestBodySpec bodySpecOverridenBaseUri = createDefaultPostRequest().uri(URI.create("/resource")); + RequestBodySpec bodySpecOverridenBaseUri = client3.post() + .uri(URI.create("/resource")); // request body specifications String bodyValue = "bodyValue"; From 047dfdef258719a8c91bde81329075f2dc5dc589 Mon Sep 17 00:00:00 2001 From: Gerardo Roza Date: Wed, 27 Jan 2021 12:28:41 -0300 Subject: [PATCH 16/16] split integration test into different test scenarios, one for each step in the webclient process --- .../web/client/WebClientIntegrationTest.java | 266 +++++++++++++----- 1 file changed, 192 insertions(+), 74 deletions(-) diff --git a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java index eddbf74868..39adf0b5c0 100644 --- a/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java +++ b/spring-5-reactive/src/test/java/com/baeldung/web/client/WebClientIntegrationTest.java @@ -33,8 +33,8 @@ import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient.RequestBodySpec; import org.springframework.web.reactive.function.client.WebClient.RequestBodyUriSpec; import org.springframework.web.reactive.function.client.WebClient.RequestHeadersSpec; +import org.springframework.web.reactive.function.client.WebClient.RequestHeadersUriSpec; import org.springframework.web.reactive.function.client.WebClient.ResponseSpec; -import org.springframework.web.reactive.function.client.WebClient.UriSpec; import org.springframework.web.reactive.function.client.WebClientRequestException; import com.baeldung.web.reactive.client.Foo; @@ -53,9 +53,13 @@ public class WebClientIntegrationTest { @LocalServerPort private int port; + private static final String BODY_VALUE = "bodyValue"; + private static final ParameterizedTypeReference> MAP_RESPONSE_REF = new ParameterizedTypeReference>() { + }; + @Test - public void givenDifferentScenarios_whenRequestsSent_thenObtainExpectedResponses() { - // WebClient + public void givenDifferentWebClientCreationMethods_whenUsed_thenObtainExpectedResponse() { + // WebClient creation WebClient client1 = WebClient.create(); WebClient client2 = WebClient.create("http://localhost:" + port); WebClient client3 = WebClient.builder() @@ -65,58 +69,149 @@ public class WebClientIntegrationTest { .defaultUriVariables(Collections.singletonMap("url", "http://localhost:8080")) .build(); - // request specification - UriSpec uriSpecPost1 = client1.method(HttpMethod.POST); - UriSpec uriSpecPost2 = client2.post(); - UriSpec requestGet = client3.get(); + // response assertions + StepVerifier.create(retrieveResponse(client1.post() + .uri("http://localhost:" + port + "/resource"))) + .expectNext("processed-bodyValue") + .verifyComplete(); + StepVerifier.create(retrieveResponse(client2)) + .expectNext("processed-bodyValue") + .verifyComplete(); + StepVerifier.create(retrieveResponse(client3)) + .expectNext("processed-bodyValue") + .verifyComplete(); + // assert response without specifying URI + StepVerifier.create(retrieveResponse(client1)) + .expectErrorMatches(ex -> WebClientRequestException.class.isAssignableFrom(ex.getClass()) && ex.getMessage() + .contains("Connection refused")) + .verify(); + } + @Test + public void givenDifferentMethodSpecifications_whenUsed_thenObtainExpectedResponse() { + // request specification + RequestBodyUriSpec uriSpecPost1 = createDefaultClient().method(HttpMethod.POST); + RequestBodyUriSpec uriSpecPost2 = createDefaultClient().post(); + RequestHeadersUriSpec requestGet = createDefaultClient().get(); + + // response assertions + StepVerifier.create(retrieveResponse(uriSpecPost1)) + .expectNext("processed-bodyValue") + .verifyComplete(); + StepVerifier.create(retrieveResponse(uriSpecPost2)) + .expectNext("processed-bodyValue") + .verifyComplete(); + StepVerifier.create(retrieveGetResponse(requestGet)) + .expectNextMatches(nextMap -> nextMap.get("field") + .equals("value")) + .verifyComplete(); + } + + @Test + public void givenDifferentUriSpecifications_whenUsed_thenObtainExpectedResponse() { // uri specification - RequestBodySpec bodySpecPost = uriSpecPost1.uri("http://localhost:" + port + "/resource"); - RequestBodySpec bodySpecPostMultipart = uriSpecPost2.uri(uriBuilder -> uriBuilder.pathSegment("resource-multipart") + RequestBodySpec bodySpecUsingString = createDefaultPostRequest().uri("/resource"); + RequestBodySpec bodySpecUsingUriBuilder = createDefaultPostRequest().uri(uriBuilder -> uriBuilder.pathSegment("resource") .build()); - RequestBodySpec fooBodySpecPost = createDefaultPostRequest().uri("/resource-foo"); - RequestBodySpec bodySpecOverridenBaseUri = client3.post() + RequestBodySpec bodySpecusingURI = createDefaultPostRequest().uri(URI.create("http://localhost:" + port + "/resource")); + RequestBodySpec bodySpecOverridenBaseUri = createDefaultPostRequest().uri(URI.create("/resource")); + RequestBodySpec bodySpecOverridenBaseUri2 = WebClient.builder() + .baseUrl("http://localhost:" + port) + .build() + .post() .uri(URI.create("/resource")); - // request body specifications - String bodyValue = "bodyValue"; - RequestHeadersSpec headersSpecPost1 = bodySpecPost.body(BodyInserters.fromPublisher(Mono.just(bodyValue), String.class)); - RequestHeadersSpec headersSpecPost2 = createDefaultPostResourceRequest().body(BodyInserters.fromValue(bodyValue)); - RequestHeadersSpec headersSpecPost3 = createDefaultPostResourceRequest().bodyValue(bodyValue); - RequestHeadersSpec headersSpecFooPost = fooBodySpecPost.body(Mono.just(new Foo("fooName")), Foo.class); - RequestHeadersSpec headersSpecGet = requestGet.uri("/resource"); + // response assertions + StepVerifier.create(retrieveResponse(bodySpecUsingString)) + .expectNext("processed-bodyValue") + .verifyComplete(); + StepVerifier.create(retrieveResponse(bodySpecUsingUriBuilder)) + .expectNext("processed-bodyValue") + .verifyComplete(); + StepVerifier.create(retrieveResponse(bodySpecusingURI)) + .expectNext("processed-bodyValue") + .verifyComplete(); + // assert sending request overriding base URI + StepVerifier.create(retrieveResponse(bodySpecOverridenBaseUri)) + .expectErrorMatches(ex -> WebClientRequestException.class.isAssignableFrom(ex.getClass()) && ex.getMessage() + .contains("Connection refused")) + .verify(); + StepVerifier.create(retrieveResponse(bodySpecOverridenBaseUri2)) + .expectErrorMatches(ex -> WebClientRequestException.class.isAssignableFrom(ex.getClass()) && ex.getMessage() + .contains("Connection refused")) + .verify(); + } - // request body specifications - using inserters + @Test + public void givenDifferentBodySpecifications_whenUsed_thenObtainExpectedResponse() { + // request body specifications + RequestHeadersSpec headersSpecPost1 = createDefaultPostResourceRequest().body(BodyInserters.fromPublisher(Mono.just(BODY_VALUE), String.class)); + RequestHeadersSpec headersSpecPost2 = createDefaultPostResourceRequest().body(BodyInserters.fromValue(BODY_VALUE)); + RequestHeadersSpec headersSpecPost3 = createDefaultPostResourceRequest().bodyValue(BODY_VALUE); + RequestHeadersSpec headersSpecFooPost = createDefaultPostRequest().uri("/resource-foo") + .body(Mono.just(new Foo("fooName")), Foo.class); + BodyInserter inserterPlainObject = BodyInserters.fromValue(new Object()); + RequestHeadersSpec headersSpecPlainObject = createDefaultPostResourceRequest().body(inserterPlainObject); + + // request body specifications - using other inserter method (multipart request) LinkedMultiValueMap map = new LinkedMultiValueMap<>(); map.add("key1", "multipartValue1"); map.add("key2", "multipartValue2"); - BodyInserter, ClientHttpRequest> inserterMultipart = BodyInserters.fromMultipartData(map); - BodyInserter inserterObject = BodyInserters.fromValue(new Object()); - BodyInserter inserterString = BodyInserters.fromValue(bodyValue); + RequestHeadersSpec headersSpecInserterMultipart = createDefaultPostRequest().uri("/resource-multipart") + .body(inserterMultipart); - RequestHeadersSpec headersSpecInserterMultipart = bodySpecPostMultipart.body(inserterMultipart); - RequestHeadersSpec headersSpecInserterObject = createDefaultPostResourceRequest().body(inserterObject); - RequestHeadersSpec headersSpecInserterString = createDefaultPostResourceRequest().body(inserterString); + // response assertions + StepVerifier.create(retrieveResponse(headersSpecPost1)) + .expectNext("processed-bodyValue") + .verifyComplete(); + StepVerifier.create(retrieveResponse(headersSpecPost2)) + .expectNext("processed-bodyValue") + .verifyComplete(); + StepVerifier.create(retrieveResponse(headersSpecPost3)) + .expectNext("processed-bodyValue") + .verifyComplete(); + StepVerifier.create(retrieveResponse(headersSpecFooPost)) + .expectNext("processedFoo-fooName") + .verifyComplete(); + StepVerifier.create(retrieveResponse(headersSpecInserterMultipart)) + .expectNext("processed-multipartValue1-multipartValue2") + .verifyComplete(); + // assert error plain `new Object()` as request body + StepVerifier.create(retrieveResponse(headersSpecPlainObject)) + .expectError(CodecException.class) + .verify(); + // assert response for request with no body + Mono> responsePostWithNoBody = createDefaultPostResourceRequest().exchangeToMono(responseHandler -> { + assertThat(responseHandler.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + return responseHandler.bodyToMono(MAP_RESPONSE_REF); + }); + StepVerifier.create(responsePostWithNoBody) + .expectNextMatches(nextMap -> nextMap.get("error") + .equals("Bad Request")) + .verifyComplete(); + } + @Test + public void givenPostSpecifications_whenHeadersAdded_thenObtainExpectedResponse() { // request header specification - RequestHeadersSpec headersSpecInserterStringWithHeaders = headersSpecInserterString.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + RequestHeadersSpec headersSpecInserterStringWithHeaders = createDefaultPostResourceRequestResponse().header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML) .acceptCharset(StandardCharsets.UTF_8) .ifNoneMatch("*") .ifModifiedSince(ZonedDateTime.now()); - // request - ResponseSpec responseSpecPostString = headersSpecInserterStringWithHeaders.retrieve(); + // response assertions + StepVerifier.create(retrieveResponse(headersSpecInserterStringWithHeaders)) + .expectNext("processed-bodyValue") + .verifyComplete(); + } + + @Test + public void givenDifferentResponseSpecifications_whenUsed_thenObtainExpectedResponse() { + ResponseSpec responseSpecPostString = createDefaultPostResourceRequestResponse().retrieve(); Mono responsePostString = responseSpecPostString.bodyToMono(String.class); - Mono responsePostMultipart = headersSpecInserterMultipart.header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) - .retrieve() - .bodyToMono(String.class); - Mono responsePostWithBody1 = headersSpecPost1.retrieve() - .bodyToMono(String.class); - Mono responsePostWithBody2 = headersSpecPost2.retrieve() - .bodyToMono(String.class); - Mono responsePostWithBody3 = headersSpecPost3.exchangeToMono(response -> { + Mono responsePostString2 = createDefaultPostResourceRequestResponse().exchangeToMono(response -> { if (response.statusCode() .equals(HttpStatus.OK)) { return response.bodyToMono(String.class); @@ -128,55 +223,37 @@ public class WebClientIntegrationTest { .flatMap(Mono::error); } }); - Mono responsePostFoo = headersSpecFooPost.retrieve() - .bodyToMono(String.class); - ParameterizedTypeReference> ref = new ParameterizedTypeReference>() { - }; - Mono> responseGet = headersSpecGet.retrieve() - .bodyToMono(ref); - Mono> responsePostWithNoBody = createDefaultPostResourceRequest().exchangeToMono(responseHandler -> { - assertThat(responseHandler.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST); - return responseHandler.bodyToMono(ref); + Mono responsePostNoBody = createDefaultPostResourceRequest().exchangeToMono(response -> { + if (response.statusCode() + .equals(HttpStatus.OK)) { + return response.bodyToMono(String.class); + } else if (response.statusCode() + .is4xxClientError()) { + return Mono.just("Error response"); + } else { + return response.createException() + .flatMap(Mono::error); + } }); - Mono responsePostOverridenBaseUri = bodySpecOverridenBaseUri.retrieve() - .bodyToMono(String.class); + Mono> responseGet = createDefaultClient().get() + .uri("/resource") + .retrieve() + .bodyToMono(MAP_RESPONSE_REF); // response assertions StepVerifier.create(responsePostString) .expectNext("processed-bodyValue") .verifyComplete(); - StepVerifier.create(responsePostMultipart) - .expectNext("processed-multipartValue1-multipartValue2") - .verifyComplete(); - StepVerifier.create(responsePostWithBody1) + StepVerifier.create(responsePostString2) .expectNext("processed-bodyValue") .verifyComplete(); - StepVerifier.create(responsePostWithBody2) - .expectNext("processed-bodyValue") - .verifyComplete(); - StepVerifier.create(responsePostWithBody3) - .expectNext("processed-bodyValue") + StepVerifier.create(responsePostNoBody) + .expectNext("Error response") .verifyComplete(); StepVerifier.create(responseGet) .expectNextMatches(nextMap -> nextMap.get("field") .equals("value")) .verifyComplete(); - StepVerifier.create(responsePostFoo) - .expectNext("processedFoo-fooName") - .verifyComplete(); - StepVerifier.create(responsePostWithNoBody) - .expectNextMatches(nextMap -> nextMap.get("error") - .equals("Bad Request")) - .verifyComplete(); - // assert sending request overriding base uri - StepVerifier.create(responsePostOverridenBaseUri) - .expectErrorMatches(ex -> WebClientRequestException.class.isAssignableFrom(ex.getClass()) && ex.getMessage() - .contains("Connection refused")) - .verify(); - // assert error plain `new Object()` as request body - StepVerifier.create(headersSpecInserterObject.exchangeToMono(response -> response.bodyToMono(String.class))) - .expectError(CodecException.class) - .verify(); } @Test @@ -202,12 +279,53 @@ public class WebClientIntegrationTest { .verify(); } + // helper methods to create default instances + private WebClient createDefaultClient() { + return WebClient.create("http://localhost:" + port); + } + private RequestBodyUriSpec createDefaultPostRequest() { - return WebClient.create("http://localhost:" + port) - .post(); + return createDefaultClient().post(); } private RequestBodySpec createDefaultPostResourceRequest() { return createDefaultPostRequest().uri("/resource"); } + + private RequestHeadersSpec createDefaultPostResourceRequestResponse() { + return createDefaultPostResourceRequest().bodyValue(BODY_VALUE); + } + + // helper methods to retrieve a response based on different steps of the process (specs) + private Mono retrieveResponse(WebClient client) { + return client.post() + .uri("/resource") + .bodyValue(BODY_VALUE) + .retrieve() + .bodyToMono(String.class); + } + + private Mono retrieveResponse(RequestBodyUriSpec spec) { + return spec.uri("/resource") + .bodyValue(BODY_VALUE) + .retrieve() + .bodyToMono(String.class); + } + + private Mono> retrieveGetResponse(RequestHeadersUriSpec spec) { + return spec.uri("/resource") + .retrieve() + .bodyToMono(MAP_RESPONSE_REF); + } + + private Mono retrieveResponse(RequestBodySpec spec) { + return spec.bodyValue(BODY_VALUE) + .retrieve() + .bodyToMono(String.class); + } + + private Mono retrieveResponse(RequestHeadersSpec spec) { + return spec.retrieve() + .bodyToMono(String.class); + } }