/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.reactive.function.client;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.codec.Pojo;
import static org.junit.Assert.*;
/**
* Integration tests using a {@link ExchangeFunction} through {@link WebClient}.
*
* @author Brian Clozel
* @author Rossen Stoyanchev
*/
public class WebClientIntegrationTests {
private MockWebServer server;
private WebClient webClient;
@Before
public void setup() {
this.server = new MockWebServer();
String baseUrl = this.server.url("/").toString();
this.webClient = WebClient.create(baseUrl);
}
@After
public void shutdown() throws Exception {
this.server.shutdown();
}
@Test
public void headers() throws Exception {
this.server.enqueue(new MockResponse().setHeader("Content-Type", "text/plain").setBody("Hello Spring!"));
Mono<HttpHeaders> result = this.webClient.get()
.uri("/greeting?name=Spring")
.exchange()
.map(response -> response.headers().asHttpHeaders());
StepVerifier.create(result)
.consumeNextWith(
httpHeaders -> {
assertEquals(MediaType.TEXT_PLAIN, httpHeaders.getContentType());
assertEquals(13L, httpHeaders.getContentLength());
})
.expectComplete()
.verify(Duration.ofSeconds(3));
RecordedRequest recordedRequest = server.takeRequest();
Assert.assertEquals(1, server.getRequestCount());
Assert.assertEquals("*/*", recordedRequest.getHeader(HttpHeaders.ACCEPT));
Assert.assertEquals("/greeting?name=Spring", recordedRequest.getPath());
}
@Test
public void plainText() throws Exception {
this.server.enqueue(new MockResponse().setBody("Hello Spring!"));
Mono<String> result = this.webClient.get()
.uri("/greeting?name=Spring")
.header("X-Test-Header", "testvalue")
.exchange()
.flatMap(response -> response.bodyToMono(String.class));
StepVerifier.create(result)
.expectNext("Hello Spring!")
.expectComplete()
.verify(Duration.ofSeconds(3));
RecordedRequest recordedRequest = server.takeRequest();
Assert.assertEquals(1, server.getRequestCount());
Assert.assertEquals("testvalue", recordedRequest.getHeader("X-Test-Header"));
Assert.assertEquals("*/*", recordedRequest.getHeader(HttpHeaders.ACCEPT));
Assert.assertEquals("/greeting?name=Spring", recordedRequest.getPath());
}
@Test
public void jsonString() throws Exception {
String content = "{\"bar\":\"barbar\",\"foo\":\"foofoo\"}";
this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
.setBody(content));
Mono<String> result = this.webClient.get()
.uri("/json")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMap(response -> response.bodyToMono(String.class));
StepVerifier.create(result)
.expectNext(content)
.expectComplete()
.verify(Duration.ofSeconds(3));
RecordedRequest recordedRequest = server.takeRequest();
Assert.assertEquals(1, server.getRequestCount());
Assert.assertEquals("/json", recordedRequest.getPath());
Assert.assertEquals("application/json", recordedRequest.getHeader(HttpHeaders.ACCEPT));
}
@Test
public void jsonStringRetrieveMono() throws Exception {
String content = "{\"bar\":\"barbar\",\"foo\":\"foofoo\"}";
this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
.setBody(content));
Mono<String> result = this.webClient.get()
.uri("/json")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(String.class);
StepVerifier.create(result)
.expectNext(content)
.expectComplete()
.verify(Duration.ofSeconds(3));
RecordedRequest recordedRequest = server.takeRequest();
Assert.assertEquals(1, server.getRequestCount());
Assert.assertEquals("/json", recordedRequest.getPath());
Assert.assertEquals("application/json", recordedRequest.getHeader(HttpHeaders.ACCEPT));
}
@Test
public void jsonStringRetrieveEntity() throws Exception {
String content = "{\"bar\":\"barbar\",\"foo\":\"foofoo\"}";
this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
.setBody(content));
Mono<ResponseEntity<String>> result = this.webClient.get()
.uri("/json")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.toEntity(String.class);
StepVerifier.create(result)
.consumeNextWith(entity -> {
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals(MediaType.APPLICATION_JSON, entity.getHeaders().getContentType());
assertEquals(31, entity.getHeaders().getContentLength());
assertEquals(content, entity.getBody());
})
.expectComplete()
.verify(Duration.ofSeconds(3));
RecordedRequest recordedRequest = server.takeRequest();
Assert.assertEquals(1, server.getRequestCount());
Assert.assertEquals("/json", recordedRequest.getPath());
Assert.assertEquals("application/json", recordedRequest.getHeader(HttpHeaders.ACCEPT));
}
@Test
public void jsonStringRetrieveEntityList() throws Exception {
String content = "[{\"bar\":\"bar1\",\"foo\":\"foo1\"}, {\"bar\":\"bar2\",\"foo\":\"foo2\"}]";
this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json").setBody(content));
Mono<ResponseEntity<List<Pojo>>> result = this.webClient.get()
.uri("/json")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.toEntityList(Pojo.class);
StepVerifier.create(result)
.consumeNextWith(entity -> {
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals(MediaType.APPLICATION_JSON, entity.getHeaders().getContentType());
assertEquals(58, entity.getHeaders().getContentLength());
Pojo pojo1 = new Pojo("foo1", "bar1");
Pojo pojo2 = new Pojo("foo2", "bar2");
assertEquals(Arrays.asList(pojo1, pojo2), entity.getBody());
})
.expectComplete()
.verify(Duration.ofSeconds(3));
RecordedRequest recordedRequest = server.takeRequest();
Assert.assertEquals(1, server.getRequestCount());
Assert.assertEquals("/json", recordedRequest.getPath());
Assert.assertEquals("application/json", recordedRequest.getHeader(HttpHeaders.ACCEPT));
}
@Test
public void jsonStringRetrieveFlux() throws Exception {
String content = "{\"bar\":\"barbar\",\"foo\":\"foofoo\"}";
this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
.setBody(content));
Flux<String> result = this.webClient.get()
.uri("/json")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToFlux(String.class);
StepVerifier.create(result)
.expectNext(content)
.expectComplete()
.verify(Duration.ofSeconds(3));
RecordedRequest recordedRequest = server.takeRequest();
Assert.assertEquals(1, server.getRequestCount());
Assert.assertEquals("/json", recordedRequest.getPath());
Assert.assertEquals("application/json", recordedRequest.getHeader(HttpHeaders.ACCEPT));
}
@Test
public void jsonPojoMono() throws Exception {
this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
.setBody("{\"bar\":\"barbar\",\"foo\":\"foofoo\"}"));
Mono<Pojo> result = this.webClient.get()
.uri("/pojo")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMap(response -> response.bodyToMono(Pojo.class));
StepVerifier.create(result)
.consumeNextWith(p -> assertEquals("barbar", p.getBar()))
.expectComplete()
.verify(Duration.ofSeconds(3));
RecordedRequest recordedRequest = server.takeRequest();
Assert.assertEquals(1, server.getRequestCount());
Assert.assertEquals("/pojo", recordedRequest.getPath());
Assert.assertEquals("application/json", recordedRequest.getHeader(HttpHeaders.ACCEPT));
}
@Test
public void jsonPojoFlux() throws Exception {
this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
.setBody("[{\"bar\":\"bar1\",\"foo\":\"foo1\"},{\"bar\":\"bar2\",\"foo\":\"foo2\"}]"));
Flux<Pojo> result = this.webClient.get()
.uri("/pojos")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMapMany(response -> response.bodyToFlux(Pojo.class));
StepVerifier.create(result)
.consumeNextWith(p -> assertThat(p.getBar(), Matchers.is("bar1")))
.consumeNextWith(p -> assertThat(p.getBar(), Matchers.is("bar2")))
.expectComplete()
.verify(Duration.ofSeconds(3));
RecordedRequest recordedRequest = server.takeRequest();
Assert.assertEquals(1, server.getRequestCount());
Assert.assertEquals("/pojos", recordedRequest.getPath());
Assert.assertEquals("application/json", recordedRequest.getHeader(HttpHeaders.ACCEPT));
}
@Test
public void postJsonPojo() throws Exception {
this.server.enqueue(new MockResponse()
.setHeader("Content-Type", "application/json")
.setBody("{\"bar\":\"BARBAR\",\"foo\":\"FOOFOO\"}"));
Mono<Pojo> result = this.webClient.post()
.uri("/pojo/capitalize")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.syncBody(new Pojo("foofoo", "barbar"))
.exchange()
.flatMap(response -> response.bodyToMono(Pojo.class));
StepVerifier.create(result)
.consumeNextWith(p -> assertEquals("BARBAR", p.getBar()))
.expectComplete()
.verify(Duration.ofSeconds(3));
RecordedRequest recordedRequest = server.takeRequest();
Assert.assertEquals(1, server.getRequestCount());
Assert.assertEquals("/pojo/capitalize", recordedRequest.getPath());
Assert.assertEquals("{\"foo\":\"foofoo\",\"bar\":\"barbar\"}", recordedRequest.getBody().readUtf8());
Assert.assertEquals("chunked", recordedRequest.getHeader(HttpHeaders.TRANSFER_ENCODING));
Assert.assertEquals("application/json", recordedRequest.getHeader(HttpHeaders.ACCEPT));
Assert.assertEquals("application/json", recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE));
}
@Test
public void cookies() throws Exception {
this.server.enqueue(new MockResponse()
.setHeader("Content-Type", "text/plain").setBody("test"));
Mono<String> result = this.webClient.get()
.uri("/test")
.cookie("testkey", "testvalue")
.exchange()
.flatMap(response -> response.bodyToMono(String.class));
StepVerifier.create(result)
.expectNext("test")
.expectComplete()
.verify(Duration.ofSeconds(3));
RecordedRequest recordedRequest = server.takeRequest();
Assert.assertEquals(1, server.getRequestCount());
Assert.assertEquals("/test", recordedRequest.getPath());
Assert.assertEquals("testkey=testvalue", recordedRequest.getHeader(HttpHeaders.COOKIE));
}
@Test
public void exchangeNotFound() throws Exception {
this.server.enqueue(new MockResponse().setResponseCode(404)
.setHeader("Content-Type", "text/plain").setBody("Not Found"));
Mono<ClientResponse> result = this.webClient.get().uri("/greeting?name=Spring").exchange();
StepVerifier.create(result)
.consumeNextWith(response -> assertEquals(HttpStatus.NOT_FOUND, response.statusCode()))
.expectComplete()
.verify(Duration.ofSeconds(3));
RecordedRequest recordedRequest = server.takeRequest();
Assert.assertEquals(1, server.getRequestCount());
Assert.assertEquals("*/*", recordedRequest.getHeader(HttpHeaders.ACCEPT));
Assert.assertEquals("/greeting?name=Spring", recordedRequest.getPath());
}
@Test
public void retrieveBodyToMonoNotFound() throws Exception {
this.server.enqueue(new MockResponse().setResponseCode(404)
.setHeader("Content-Type", "text/plain").setBody("Not Found"));
Mono<String> result = this.webClient.get()
.uri("/greeting?name=Spring")
.retrieve()
.bodyToMono(String.class);
StepVerifier.create(result)
.expectError(WebClientException.class)
.verify(Duration.ofSeconds(3));
RecordedRequest recordedRequest = server.takeRequest();
Assert.assertEquals(1, server.getRequestCount());
Assert.assertEquals("*/*", recordedRequest.getHeader(HttpHeaders.ACCEPT));
Assert.assertEquals("/greeting?name=Spring", recordedRequest.getPath());
}
@Test
public void retrieveToEntityNotFound() throws Exception {
this.server.enqueue(new MockResponse().setResponseCode(404)
.setHeader("Content-Type", "text/plain").setBody("Not Found"));
Mono<ResponseEntity<String>> result = this.webClient.get()
.uri("/greeting?name=Spring")
.retrieve()
.toEntity(String.class);
StepVerifier.create(result)
.consumeNextWith(response -> assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()))
.expectComplete()
.verify(Duration.ofSeconds(3));
RecordedRequest recordedRequest = server.takeRequest();
Assert.assertEquals(1, server.getRequestCount());
Assert.assertEquals("*/*", recordedRequest.getHeader(HttpHeaders.ACCEPT));
Assert.assertEquals("/greeting?name=Spring", recordedRequest.getPath());
}
@Test
public void filter() throws Exception {
this.server.enqueue(new MockResponse().setHeader("Content-Type", "text/plain").setBody("Hello Spring!"));
WebClient filteredClient = this.webClient.filter(
(request, next) -> {
ClientRequest filteredRequest = ClientRequest.from(request).header("foo", "bar").build();
return next.exchange(filteredRequest);
});
Mono<String> result = filteredClient.get()
.uri("/greeting?name=Spring")
.retrieve()
.bodyToMono(String.class);
StepVerifier.create(result)
.expectNext("Hello Spring!")
.expectComplete()
.verify(Duration.ofSeconds(3));
RecordedRequest recordedRequest = server.takeRequest();
Assert.assertEquals(1, server.getRequestCount());
Assert.assertEquals("bar", recordedRequest.getHeader("foo"));
}
@Test
public void errorHandlingFilter() throws Exception {
ExchangeFilterFunction filter = ExchangeFilterFunction.ofResponseProcessor(
clientResponse -> {
List<String> headerValues = clientResponse.headers().header("Foo");
return headerValues.isEmpty() ? Mono.error(
new MyException("Response does not contain Foo header")) :
Mono.just(clientResponse);
}
);
WebClient filteredClient = this.webClient.filter(filter);
// header not present
this.server.enqueue(new MockResponse().setHeader("Content-Type", "text/plain").setBody("Hello Spring!"));
Mono<String> result = filteredClient.get()
.uri("/greeting?name=Spring")
.retrieve()
.bodyToMono(String.class);
StepVerifier.create(result)
.expectError(MyException.class)
.verify(Duration.ofSeconds(3));
// header present
this.server.enqueue(new MockResponse().setHeader("Content-Type", "text/plain")
.setHeader("Foo", "Bar")
.setBody("Hello Spring!"));
result = filteredClient.get()
.uri("/greeting?name=Spring")
.retrieve()
.bodyToMono(String.class);
StepVerifier.create(result)
.expectNext("Hello Spring!")
.expectComplete()
.verify(Duration.ofSeconds(3));
Assert.assertEquals(2, server.getRequestCount());
}
@SuppressWarnings("serial")
private static class MyException extends RuntimeException {
public MyException(String message) {
super(message);
}
}
}