WebTestClient polish and minor refactoring
This commit is contained in:
parent
d924538211
commit
20be40bf64
|
|
@ -22,12 +22,9 @@ import java.time.ZonedDateTime;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.reactivestreams.Publisher;
|
import org.reactivestreams.Publisher;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
|
|
@ -36,7 +33,6 @@ import reactor.core.publisher.Mono;
|
||||||
import org.springframework.core.ResolvableType;
|
import org.springframework.core.ResolvableType;
|
||||||
import org.springframework.core.io.buffer.DataBuffer;
|
import org.springframework.core.io.buffer.DataBuffer;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||||
import org.springframework.http.client.reactive.ClientHttpRequest;
|
import org.springframework.http.client.reactive.ClientHttpRequest;
|
||||||
|
|
@ -48,9 +44,9 @@ import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
|
||||||
import org.springframework.web.reactive.function.client.WebClient;
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
import org.springframework.web.util.UriBuilder;
|
import org.springframework.web.util.UriBuilder;
|
||||||
|
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
import static org.springframework.test.util.AssertionErrors.assertEquals;
|
import static org.springframework.test.util.AssertionErrors.assertEquals;
|
||||||
import static org.springframework.test.util.AssertionErrors.assertTrue;
|
import static org.springframework.test.util.AssertionErrors.assertTrue;
|
||||||
import static org.springframework.test.util.AssertionErrors.fail;
|
|
||||||
import static org.springframework.web.reactive.function.BodyExtractors.toDataBuffers;
|
import static org.springframework.web.reactive.function.BodyExtractors.toDataBuffers;
|
||||||
import static org.springframework.web.reactive.function.BodyExtractors.toFlux;
|
import static org.springframework.web.reactive.function.BodyExtractors.toFlux;
|
||||||
import static org.springframework.web.reactive.function.BodyExtractors.toMono;
|
import static org.springframework.web.reactive.function.BodyExtractors.toMono;
|
||||||
|
|
@ -65,33 +61,30 @@ class DefaultWebTestClient implements WebTestClient {
|
||||||
|
|
||||||
private final WebClient webClient;
|
private final WebClient webClient;
|
||||||
|
|
||||||
private final Duration responseTimeout;
|
private final WebTestClientConnector webTestClientConnector;
|
||||||
|
|
||||||
private final WiretapConnectorListener connectorListener;
|
private final Duration timeout;
|
||||||
|
|
||||||
|
private final AtomicLong requestIndex = new AtomicLong();
|
||||||
|
|
||||||
|
|
||||||
DefaultWebTestClient(WebClient.Builder webClientBuilder, ClientHttpConnector connector, Duration timeout) {
|
DefaultWebTestClient(WebClient.Builder webClientBuilder, ClientHttpConnector connector, Duration timeout) {
|
||||||
Assert.notNull(webClientBuilder, "WebClient.Builder is required");
|
Assert.notNull(webClientBuilder, "WebClient.Builder is required");
|
||||||
|
|
||||||
WiretapConnector wiretapConnector = new WiretapConnector(connector);
|
this.webTestClientConnector = new WebTestClientConnector(connector);
|
||||||
webClientBuilder.clientConnector(wiretapConnector);
|
this.webClient = webClientBuilder.clientConnector(this.webTestClientConnector).build();
|
||||||
|
this.timeout = (timeout != null ? timeout : Duration.ofSeconds(5));
|
||||||
this.connectorListener = new WiretapConnectorListener();
|
|
||||||
wiretapConnector.addListener(this.connectorListener);
|
|
||||||
|
|
||||||
this.webClient = webClientBuilder.build();
|
|
||||||
this.responseTimeout = (timeout != null ? timeout : Duration.ofSeconds(5));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private DefaultWebTestClient(DefaultWebTestClient webTestClient, ExchangeFilterFunction filter) {
|
private DefaultWebTestClient(DefaultWebTestClient webTestClient, ExchangeFilterFunction filter) {
|
||||||
this.webClient = webTestClient.webClient.filter(filter);
|
this.webClient = webTestClient.webClient.filter(filter);
|
||||||
this.connectorListener = webTestClient.connectorListener;
|
this.timeout = webTestClient.timeout;
|
||||||
this.responseTimeout = webTestClient.responseTimeout;
|
this.webTestClientConnector = webTestClient.webTestClientConnector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Duration getTimeout() {
|
private Duration getTimeout() {
|
||||||
return this.responseTimeout;
|
return this.timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -141,7 +134,6 @@ class DefaultWebTestClient implements WebTestClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private class DefaultUriSpec implements UriSpec {
|
private class DefaultUriSpec implements UriSpec {
|
||||||
|
|
||||||
private final WebClient.UriSpec uriSpec;
|
private final WebClient.UriSpec uriSpec;
|
||||||
|
|
@ -181,7 +173,8 @@ class DefaultWebTestClient implements WebTestClient {
|
||||||
|
|
||||||
DefaultHeaderSpec(WebClient.HeaderSpec spec) {
|
DefaultHeaderSpec(WebClient.HeaderSpec spec) {
|
||||||
this.headerSpec = spec;
|
this.headerSpec = spec;
|
||||||
this.requestId = connectorListener.registerRequestId(spec);
|
this.requestId = String.valueOf(requestIndex.incrementAndGet());
|
||||||
|
this.headerSpec.header(WebTestClientConnector.REQUEST_ID_HEADER_NAME, this.requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -247,29 +240,25 @@ class DefaultWebTestClient implements WebTestClient {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResponseSpec exchange() {
|
public ResponseSpec exchange() {
|
||||||
return createResponseSpec(this.headerSpec.exchange());
|
return toResponseSpec(this.headerSpec.exchange());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> ResponseSpec exchange(BodyInserter<T, ? super ClientHttpRequest> inserter) {
|
public <T> ResponseSpec exchange(BodyInserter<T, ? super ClientHttpRequest> inserter) {
|
||||||
return createResponseSpec(this.headerSpec.exchange(inserter));
|
return toResponseSpec(this.headerSpec.exchange(inserter));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T, S extends Publisher<T>> ResponseSpec exchange(S publisher, Class<T> elementClass) {
|
public <T, S extends Publisher<T>> ResponseSpec exchange(S publisher, Class<T> elementClass) {
|
||||||
return createResponseSpec(this.headerSpec.exchange(publisher, elementClass));
|
return toResponseSpec(this.headerSpec.exchange(publisher, elementClass));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected DefaultResponseSpec createResponseSpec(Mono<ClientResponse> responseMono) {
|
private DefaultResponseSpec toResponseSpec(Mono<ClientResponse> responseMono) {
|
||||||
ClientResponse response = responseMono.block(getTimeout());
|
ClientResponse response = responseMono.block(getTimeout());
|
||||||
WiretapConnector.Info info = connectorListener.retrieveRequest(this.requestId);
|
ClientHttpRequest request = webTestClientConnector.claimRequest(this.requestId);
|
||||||
HttpMethod method = info.getMethod();
|
ExchangeResult<Flux<DataBuffer>> result = ExchangeResult.create(request, response);
|
||||||
URI url = info.getUrl();
|
|
||||||
HttpHeaders headers = info.getRequestHeaders();
|
|
||||||
ExchangeResult<Flux<DataBuffer>> result = ExchangeResult.fromResponse(method, url, headers, response);
|
|
||||||
return new DefaultResponseSpec(result, response);
|
return new DefaultResponseSpec(result, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private abstract class ResponseSpecSupport {
|
private abstract class ResponseSpecSupport {
|
||||||
|
|
@ -284,11 +273,6 @@ class DefaultWebTestClient implements WebTestClient {
|
||||||
this.response = response;
|
this.response = response;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResponseSpecSupport(ResponseSpecSupport responseSpec) {
|
|
||||||
this.exchangeResult = responseSpec.getExchangeResult();
|
|
||||||
this.response = responseSpec.getResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected ExchangeResult<Flux<DataBuffer>> getExchangeResult() {
|
protected ExchangeResult<Flux<DataBuffer>> getExchangeResult() {
|
||||||
return this.exchangeResult;
|
return this.exchangeResult;
|
||||||
|
|
@ -298,10 +282,6 @@ class DefaultWebTestClient implements WebTestClient {
|
||||||
return this.response;
|
return this.response;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected HttpHeaders getResponseHeaders() {
|
|
||||||
return getExchangeResult().getResponseHeaders();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected <T> ExchangeResult<T> createResultWithDecodedBody(T body) {
|
protected <T> ExchangeResult<T> createResultWithDecodedBody(T body) {
|
||||||
return ExchangeResult.withDecodedBody(this.exchangeResult, body);
|
return ExchangeResult.withDecodedBody(this.exchangeResult, body);
|
||||||
}
|
}
|
||||||
|
|
@ -311,19 +291,28 @@ class DefaultWebTestClient implements WebTestClient {
|
||||||
private class DefaultResponseSpec extends ResponseSpecSupport implements ResponseSpec {
|
private class DefaultResponseSpec extends ResponseSpecSupport implements ResponseSpec {
|
||||||
|
|
||||||
|
|
||||||
public DefaultResponseSpec(ExchangeResult<Flux<DataBuffer>> result, ClientResponse response) {
|
public DefaultResponseSpec(ExchangeResult<Flux<DataBuffer>> exchangeResult, ClientResponse response) {
|
||||||
super(result, response);
|
super(exchangeResult, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StatusAssertions expectStatus() {
|
public StatusAssertions expectStatus() {
|
||||||
return new StatusAssertions(getResponse().statusCode(), this);
|
return new StatusAssertions(getExchangeResult(), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HeaderAssertions expectHeader() {
|
public HeaderAssertions expectHeader() {
|
||||||
return new HeaderAssertions(getResponseHeaders(), this);
|
return new HeaderAssertions(getExchangeResult(), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TypeBodySpec expectBody(Class<?> elementType) {
|
||||||
|
return expectBody(ResolvableType.forClass(elementType));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TypeBodySpec expectBody(ResolvableType elementType) {
|
||||||
|
return new DefaultTypeBodySpec(this, elementType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -331,16 +320,6 @@ class DefaultWebTestClient implements WebTestClient {
|
||||||
return new DefaultBodySpec(this);
|
return new DefaultBodySpec(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public ElementBodySpec expectBody(Class<?> elementType) {
|
|
||||||
return expectBody(ResolvableType.forClass(elementType));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ElementBodySpec expectBody(ResolvableType elementType) {
|
|
||||||
return new DefaultElementBodySpec(this, elementType);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResponseSpec consumeWith(Consumer<ExchangeResult<Flux<DataBuffer>>> consumer) {
|
public ResponseSpec consumeWith(Consumer<ExchangeResult<Flux<DataBuffer>>> consumer) {
|
||||||
consumer.accept(getExchangeResult());
|
consumer.accept(getExchangeResult());
|
||||||
|
|
@ -353,97 +332,13 @@ class DefaultWebTestClient implements WebTestClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DefaultBodySpec extends ResponseSpecSupport implements BodySpec {
|
private class DefaultTypeBodySpec extends ResponseSpecSupport implements TypeBodySpec {
|
||||||
|
|
||||||
|
|
||||||
public DefaultBodySpec(ResponseSpecSupport responseSpec) {
|
|
||||||
super(responseSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ExchangeResult<Void> isEmpty() {
|
|
||||||
DataBuffer buffer = getResponse().body(toDataBuffers()).blockFirst(getTimeout());
|
|
||||||
assertTrue("Expected empty body", buffer == null);
|
|
||||||
return createResultWithDecodedBody(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MapBodySpec map(Class<?> keyType, Class<?> valueType) {
|
|
||||||
return map(ResolvableType.forClass(keyType), ResolvableType.forClass(valueType));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MapBodySpec map(ResolvableType keyType, ResolvableType valueType) {
|
|
||||||
return new DefaultMapBodySpec(this, keyType, valueType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class DefaultMapBodySpec extends ResponseSpecSupport implements MapBodySpec {
|
|
||||||
|
|
||||||
private final Map<?, ?> body;
|
|
||||||
|
|
||||||
|
|
||||||
public DefaultMapBodySpec(ResponseSpecSupport spec, ResolvableType keyType, ResolvableType valueType) {
|
|
||||||
super(spec);
|
|
||||||
ResolvableType mapType = ResolvableType.forClassWithGenerics(Map.class, keyType, valueType);
|
|
||||||
this.body = (Map<?, ?>) spec.getResponse().body(toMono(mapType)).block(getTimeout());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <K, V> ExchangeResult<Map<K, V>> isEqualTo(Map<K, V> expected) {
|
|
||||||
return returnResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MapBodySpec hasSize(int size) {
|
|
||||||
assertEquals("Response body map size", size, this.body.size());
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MapBodySpec contains(Object key, Object value) {
|
|
||||||
assertEquals("Response body map value for key " + key, value, this.body.get(key));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MapBodySpec containsKeys(Object... keys) {
|
|
||||||
List<Object> missing = Arrays.stream(keys)
|
|
||||||
.filter(key -> !this.body.containsKey(key))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
if (!missing.isEmpty()) {
|
|
||||||
fail("Response body map does not contain keys " + Arrays.toString(keys));
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MapBodySpec containsValues(Object... values) {
|
|
||||||
List<Object> missing = Arrays.stream(values)
|
|
||||||
.filter(value -> !this.body.containsValue(value))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
if (!missing.isEmpty()) {
|
|
||||||
fail("Response body map does not contain values " + Arrays.toString(values));
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public <K, V> ExchangeResult<Map<K, V>> returnResult() {
|
|
||||||
return createResultWithDecodedBody((Map<K, V>) this.body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class DefaultElementBodySpec extends ResponseSpecSupport implements ElementBodySpec {
|
|
||||||
|
|
||||||
private final ResolvableType elementType;
|
private final ResolvableType elementType;
|
||||||
|
|
||||||
|
|
||||||
public DefaultElementBodySpec(ResponseSpecSupport spec, ResolvableType elementType) {
|
public DefaultTypeBodySpec(DefaultResponseSpec spec, ResolvableType elementType) {
|
||||||
super(spec);
|
super(spec.getExchangeResult(), spec.getResponse());
|
||||||
this.elementType = elementType;
|
this.elementType = elementType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -455,7 +350,7 @@ class DefaultWebTestClient implements WebTestClient {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ListBodySpec list() {
|
public ListBodySpec list() {
|
||||||
return new DefaultListBodySpec(this, this.elementType, -1);
|
return list(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -470,14 +365,13 @@ class DefaultWebTestClient implements WebTestClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DefaultSingleValueBodySpec extends ResponseSpecSupport
|
private class DefaultSingleValueBodySpec extends ResponseSpecSupport implements SingleValueBodySpec {
|
||||||
implements SingleValueBodySpec {
|
|
||||||
|
|
||||||
private final Object body;
|
private final Object body;
|
||||||
|
|
||||||
|
|
||||||
public DefaultSingleValueBodySpec(ResponseSpecSupport spec, ResolvableType elementType) {
|
public DefaultSingleValueBodySpec(DefaultTypeBodySpec spec, ResolvableType elementType) {
|
||||||
super(spec);
|
super(spec.getExchangeResult(), spec.getResponse());
|
||||||
this.body = getResponse().body(toMono(elementType)).block(getTimeout());
|
this.body = getResponse().body(toMono(elementType)).block(getTimeout());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -495,14 +389,13 @@ class DefaultWebTestClient implements WebTestClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DefaultListBodySpec extends ResponseSpecSupport
|
private class DefaultListBodySpec extends ResponseSpecSupport implements ListBodySpec {
|
||||||
implements ListBodySpec {
|
|
||||||
|
|
||||||
private final List<?> body;
|
private final List<?> body;
|
||||||
|
|
||||||
|
|
||||||
public DefaultListBodySpec(ResponseSpecSupport spec, ResolvableType elementType, int elementCount) {
|
public DefaultListBodySpec(DefaultTypeBodySpec spec, ResolvableType elementType, int elementCount) {
|
||||||
super(spec);
|
super(spec.getExchangeResult(), spec.getResponse());
|
||||||
Flux<?> flux = getResponse().body(toFlux(elementType));
|
Flux<?> flux = getResponse().body(toFlux(elementType));
|
||||||
if (elementCount >= 0) {
|
if (elementCount >= 0) {
|
||||||
flux = flux.take(elementCount);
|
flux = flux.take(elementCount);
|
||||||
|
|
@ -545,33 +438,79 @@ class DefaultWebTestClient implements WebTestClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class DefaultBodySpec extends ResponseSpecSupport implements BodySpec {
|
||||||
private static class WiretapConnectorListener implements Consumer<WiretapConnector.Info> {
|
|
||||||
|
|
||||||
private static final String REQUEST_ID_HEADER_NAME = "request-id";
|
|
||||||
|
|
||||||
|
|
||||||
private final AtomicLong index = new AtomicLong();
|
public DefaultBodySpec(DefaultResponseSpec spec) {
|
||||||
|
super(spec.getExchangeResult(), spec.getResponse());
|
||||||
private final Map<String, WiretapConnector.Info> exchanges = new ConcurrentHashMap<>();
|
}
|
||||||
|
|
||||||
|
|
||||||
public String registerRequestId(WebClient.HeaderSpec headerSpec) {
|
@Override
|
||||||
String requestId = String.valueOf(this.index.incrementAndGet());
|
public ExchangeResult<Void> isEmpty() {
|
||||||
headerSpec.header(REQUEST_ID_HEADER_NAME, requestId);
|
DataBuffer buffer = getResponse().body(toDataBuffers()).blockFirst(getTimeout());
|
||||||
return requestId;
|
assertTrue("Expected empty body", buffer == null);
|
||||||
|
return createResultWithDecodedBody(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void accept(WiretapConnector.Info info) {
|
public MapBodySpec map(Class<?> keyType, Class<?> valueType) {
|
||||||
Optional.ofNullable(info.getRequestHeaders().getFirst(REQUEST_ID_HEADER_NAME))
|
return map(ResolvableType.forClass(keyType), ResolvableType.forClass(valueType));
|
||||||
.ifPresent(id -> this.exchanges.put(id, info));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public WiretapConnector.Info retrieveRequest(String requestId) {
|
@Override
|
||||||
WiretapConnector.Info info = this.exchanges.remove(requestId);
|
public MapBodySpec map(ResolvableType keyType, ResolvableType valueType) {
|
||||||
Assert.notNull(info, "No match for request-id=" + requestId);
|
return new DefaultMapBodySpec(this, keyType, valueType);
|
||||||
return info;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DefaultMapBodySpec extends ResponseSpecSupport implements MapBodySpec {
|
||||||
|
|
||||||
|
private final Map<?, ?> body;
|
||||||
|
|
||||||
|
|
||||||
|
public DefaultMapBodySpec(DefaultBodySpec spec, ResolvableType keyType, ResolvableType valueType) {
|
||||||
|
super(spec.getExchangeResult(), spec.getResponse());
|
||||||
|
ResolvableType mapType = ResolvableType.forClassWithGenerics(Map.class, keyType, valueType);
|
||||||
|
this.body = (Map<?, ?>) spec.getResponse().body(toMono(mapType)).block(getTimeout());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <K, V> ExchangeResult<Map<K, V>> isEqualTo(Map<K, V> expected) {
|
||||||
|
return returnResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MapBodySpec hasSize(int size) {
|
||||||
|
assertEquals("Response body map size", size, this.body.size());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MapBodySpec contains(Object key, Object value) {
|
||||||
|
assertEquals("Response body map value for key " + key, value, this.body.get(key));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MapBodySpec containsKeys(Object... keys) {
|
||||||
|
List<?> missing = Arrays.stream(keys).filter(k -> !this.body.containsKey(k)).collect(toList());
|
||||||
|
assertTrue("Response body map does not contain keys " + missing, missing.isEmpty());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MapBodySpec containsValues(Object... values) {
|
||||||
|
List<?> missing = Arrays.stream(values).filter(v -> !this.body.containsValue(v)).collect(toList());
|
||||||
|
assertTrue("Response body map does not contain values " + missing, missing.isEmpty());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <K, V> ExchangeResult<Map<K, V>> returnResult() {
|
||||||
|
return createResultWithDecodedBody((Map<K, V>) this.body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import org.springframework.core.io.buffer.DataBuffer;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.client.reactive.ClientHttpRequest;
|
||||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||||
|
|
||||||
import static org.springframework.web.reactive.function.BodyExtractors.toDataBuffers;
|
import static org.springframework.web.reactive.function.BodyExtractors.toDataBuffers;
|
||||||
|
|
@ -111,19 +112,16 @@ public class ExchangeResult<T> {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an instance from a ClientResponse (body not yet consumed).
|
* Create from ClientHttpRequest and ClientResponse (body not yet consumed).
|
||||||
*/
|
*/
|
||||||
static ExchangeResult<Flux<DataBuffer>> fromResponse(HttpMethod method, URI url,
|
static ExchangeResult<Flux<DataBuffer>> create(ClientHttpRequest request, ClientResponse response) {
|
||||||
HttpHeaders requestHeaders, ClientResponse response) {
|
return new ExchangeResult<>(request.getMethod(), request.getURI(), request.getHeaders(),
|
||||||
|
response.statusCode(), response.headers().asHttpHeaders(),
|
||||||
HttpStatus status = response.statusCode();
|
response.body(toDataBuffers()));
|
||||||
HttpHeaders responseHeaders = response.headers().asHttpHeaders();
|
|
||||||
Flux<DataBuffer> body = response.body(toDataBuffers());
|
|
||||||
return new ExchangeResult<>(method, url, requestHeaders, status, responseHeaders, body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Re-create the result with a generic type matching the decoded body.
|
* Re-create with decoded body (possibly still not consumed).
|
||||||
*/
|
*/
|
||||||
static <T> ExchangeResult<T> withDecodedBody(ExchangeResult<?> result, T body) {
|
static <T> ExchangeResult<T> withDecodedBody(ExchangeResult<?> result, T body) {
|
||||||
return new ExchangeResult<>(result.getMethod(), result.getUrl(), result.getRequestHeaders(),
|
return new ExchangeResult<>(result.getMethod(), result.getUrl(), result.getRequestHeaders(),
|
||||||
|
|
|
||||||
|
|
@ -29,33 +29,42 @@ import static org.springframework.test.util.AssertionErrors.assertEquals;
|
||||||
import static org.springframework.test.util.AssertionErrors.assertTrue;
|
import static org.springframework.test.util.AssertionErrors.assertTrue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides methods for HTTP header assertions.
|
* Assertions on headers of the response.
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
* @see ResponseAssertions#header()
|
* @see WebTestClient.ResponseSpec#expectHeader()
|
||||||
*/
|
*/
|
||||||
public class HeaderAssertions {
|
public class HeaderAssertions {
|
||||||
|
|
||||||
private final HttpHeaders headers;
|
private final ExchangeResult<?> exchangeResult;
|
||||||
|
|
||||||
private final WebTestClient.ResponseSpec responseSpec;
|
private final WebTestClient.ResponseSpec responseSpec;
|
||||||
|
|
||||||
|
|
||||||
public HeaderAssertions(HttpHeaders headers, WebTestClient.ResponseSpec responseSpec) {
|
HeaderAssertions(ExchangeResult<?> exchangeResult, WebTestClient.ResponseSpec responseSpec) {
|
||||||
this.headers = headers;
|
this.exchangeResult = exchangeResult;
|
||||||
this.responseSpec = responseSpec;
|
this.responseSpec = responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expect a header with the given name to match the specified values.
|
||||||
|
*/
|
||||||
public WebTestClient.ResponseSpec valueEquals(String headerName, String... values) {
|
public WebTestClient.ResponseSpec valueEquals(String headerName, String... values) {
|
||||||
List<String> actual = this.headers.get(headerName);
|
List<String> actual = getHeaders().get(headerName);
|
||||||
assertEquals("Response header [" + headerName + "]", Arrays.asList(values), actual);
|
assertEquals("Response header [" + headerName + "]", Arrays.asList(values), actual);
|
||||||
return this.responseSpec;
|
return this.responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expect a header with the given name whose first value matches the
|
||||||
|
* provided regex pattern.
|
||||||
|
* @param headerName the header name
|
||||||
|
* @param pattern String pattern to pass to {@link Pattern#compile(String)}
|
||||||
|
*/
|
||||||
public WebTestClient.ResponseSpec valueMatches(String headerName, String pattern) {
|
public WebTestClient.ResponseSpec valueMatches(String headerName, String pattern) {
|
||||||
List<String> values = this.headers.get(headerName);
|
List<String> values = getHeaders().get(headerName);
|
||||||
String value = CollectionUtils.isEmpty(values) ? "" : values.get(0);
|
String value = CollectionUtils.isEmpty(values) ? "" : values.get(0);
|
||||||
boolean match = Pattern.compile(pattern).matcher(value).matches();
|
boolean match = Pattern.compile(pattern).matcher(value).matches();
|
||||||
String message = "Response header " + headerName + "=\'" + value + "\' does not match " + pattern;
|
String message = "Response header " + headerName + "=\'" + value + "\' does not match " + pattern;
|
||||||
|
|
@ -63,40 +72,65 @@ public class HeaderAssertions {
|
||||||
return this.responseSpec;
|
return this.responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebTestClient.ResponseSpec cacheControlEquals(CacheControl cacheControl) {
|
/**
|
||||||
String actual = this.headers.getCacheControl();
|
* Expect a "Cache-Control" header with the given value.
|
||||||
|
*/
|
||||||
|
public WebTestClient.ResponseSpec cacheControl(CacheControl cacheControl) {
|
||||||
|
String actual = getHeaders().getCacheControl();
|
||||||
assertEquals("Response header Cache-Control", cacheControl.getHeaderValue(), actual);
|
assertEquals("Response header Cache-Control", cacheControl.getHeaderValue(), actual);
|
||||||
return this.responseSpec;
|
return this.responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebTestClient.ResponseSpec contentDispositionEquals(ContentDisposition contentDisposition) {
|
/**
|
||||||
ContentDisposition actual = this.headers.getContentDisposition();
|
* Expect a "Content-Disposition" header with the given value.
|
||||||
|
*/
|
||||||
|
public WebTestClient.ResponseSpec contentDisposition(ContentDisposition contentDisposition) {
|
||||||
|
ContentDisposition actual = getHeaders().getContentDisposition();
|
||||||
assertEquals("Response header Content-Disposition", contentDisposition, actual);
|
assertEquals("Response header Content-Disposition", contentDisposition, actual);
|
||||||
return this.responseSpec;
|
return this.responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebTestClient.ResponseSpec contentLengthEquals(long contentLength) {
|
/**
|
||||||
long actual = this.headers.getContentLength();
|
* Expect a "Content-Length" header with the given value.
|
||||||
|
*/
|
||||||
|
public WebTestClient.ResponseSpec contentLength(long contentLength) {
|
||||||
|
long actual = getHeaders().getContentLength();
|
||||||
assertEquals("Response header Content-Length", contentLength, actual);
|
assertEquals("Response header Content-Length", contentLength, actual);
|
||||||
return this.responseSpec;
|
return this.responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebTestClient.ResponseSpec contentTypeEquals(MediaType mediaType) {
|
/**
|
||||||
MediaType actual = this.headers.getContentType();
|
* Expect a "Content-Type" header with the given value.
|
||||||
|
*/
|
||||||
|
public WebTestClient.ResponseSpec contentType(MediaType mediaType) {
|
||||||
|
MediaType actual = getHeaders().getContentType();
|
||||||
assertEquals("Response header Content-Type", mediaType, actual);
|
assertEquals("Response header Content-Type", mediaType, actual);
|
||||||
return this.responseSpec;
|
return this.responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebTestClient.ResponseSpec expiresEquals(int expires) {
|
/**
|
||||||
long actual = this.headers.getExpires();
|
* Expect an "Expires" header with the given value.
|
||||||
|
*/
|
||||||
|
public WebTestClient.ResponseSpec expires(int expires) {
|
||||||
|
long actual = getHeaders().getExpires();
|
||||||
assertEquals("Response header Expires", expires, actual);
|
assertEquals("Response header Expires", expires, actual);
|
||||||
return this.responseSpec;
|
return this.responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebTestClient.ResponseSpec lastModifiedEquals(int lastModified) {
|
/**
|
||||||
long actual = this.headers.getLastModified();
|
* Expect a "Last-Modified" header with the given value.
|
||||||
|
*/
|
||||||
|
public WebTestClient.ResponseSpec lastModified(int lastModified) {
|
||||||
|
long actual = getHeaders().getLastModified();
|
||||||
assertEquals("Response header Last-Modified", lastModified, actual);
|
assertEquals("Response header Last-Modified", lastModified, actual);
|
||||||
return this.responseSpec;
|
return this.responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Private methods
|
||||||
|
|
||||||
|
private HttpHeaders getHeaders() {
|
||||||
|
return this.exchangeResult.getResponseHeaders();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,22 +20,22 @@ import org.springframework.http.HttpStatus;
|
||||||
import static org.springframework.test.util.AssertionErrors.assertEquals;
|
import static org.springframework.test.util.AssertionErrors.assertEquals;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assertions on the status of a response.
|
* Assertions on the response status.
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
* @see ResponseAssertions#status()
|
* @see WebTestClient.ResponseSpec#expectStatus()
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class StatusAssertions {
|
public class StatusAssertions {
|
||||||
|
|
||||||
private final HttpStatus httpStatus;
|
private final ExchangeResult<?> exchangeResult;
|
||||||
|
|
||||||
private final WebTestClient.ResponseSpec responseSpec;
|
private final WebTestClient.ResponseSpec responseSpec;
|
||||||
|
|
||||||
|
|
||||||
StatusAssertions(HttpStatus status, WebTestClient.ResponseSpec responseSpec) {
|
StatusAssertions(ExchangeResult<?> exchangeResult, WebTestClient.ResponseSpec responseSpec) {
|
||||||
this.httpStatus = status;
|
this.exchangeResult = exchangeResult;
|
||||||
this.responseSpec = responseSpec;
|
this.responseSpec = responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,7 +44,7 @@ public class StatusAssertions {
|
||||||
* Assert the response status as an {@link HttpStatus}.
|
* Assert the response status as an {@link HttpStatus}.
|
||||||
*/
|
*/
|
||||||
public WebTestClient.ResponseSpec isEqualTo(HttpStatus status) {
|
public WebTestClient.ResponseSpec isEqualTo(HttpStatus status) {
|
||||||
assertEquals("Response status", status, this.httpStatus);
|
assertEquals("Response status", status, getStatus());
|
||||||
return this.responseSpec;
|
return this.responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,7 +52,7 @@ public class StatusAssertions {
|
||||||
* Assert the response status as an integer.
|
* Assert the response status as an integer.
|
||||||
*/
|
*/
|
||||||
public WebTestClient.ResponseSpec isEqualTo(int status) {
|
public WebTestClient.ResponseSpec isEqualTo(int status) {
|
||||||
assertEquals("Response status", status, this.httpStatus.value());
|
assertEquals("Response status", status, getStatus().value());
|
||||||
return this.responseSpec;
|
return this.responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,7 +60,7 @@ public class StatusAssertions {
|
||||||
* Assert the response status code is {@code HttpStatus.OK} (200).
|
* Assert the response status code is {@code HttpStatus.OK} (200).
|
||||||
*/
|
*/
|
||||||
public WebTestClient.ResponseSpec isOk() {
|
public WebTestClient.ResponseSpec isOk() {
|
||||||
assertEquals("Status", HttpStatus.OK, this.httpStatus);
|
assertEquals("Status", HttpStatus.OK, getStatus());
|
||||||
return this.responseSpec;
|
return this.responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,7 +68,7 @@ public class StatusAssertions {
|
||||||
* Assert the response status code is {@code HttpStatus.CREATED} (201).
|
* Assert the response status code is {@code HttpStatus.CREATED} (201).
|
||||||
*/
|
*/
|
||||||
public WebTestClient.ResponseSpec isCreated() {
|
public WebTestClient.ResponseSpec isCreated() {
|
||||||
assertEquals("Status", HttpStatus.CREATED, this.httpStatus);
|
assertEquals("Status", HttpStatus.CREATED, getStatus());
|
||||||
return this.responseSpec;
|
return this.responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,7 +76,7 @@ public class StatusAssertions {
|
||||||
* Assert the response status code is {@code HttpStatus.ACCEPTED} (202).
|
* Assert the response status code is {@code HttpStatus.ACCEPTED} (202).
|
||||||
*/
|
*/
|
||||||
public WebTestClient.ResponseSpec isAccepted() {
|
public WebTestClient.ResponseSpec isAccepted() {
|
||||||
assertEquals("Status", HttpStatus.ACCEPTED, this.httpStatus);
|
assertEquals("Status", HttpStatus.ACCEPTED, getStatus());
|
||||||
return this.responseSpec;
|
return this.responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,7 +84,7 @@ public class StatusAssertions {
|
||||||
* Assert the response status code is {@code HttpStatus.NO_CONTENT} (204).
|
* Assert the response status code is {@code HttpStatus.NO_CONTENT} (204).
|
||||||
*/
|
*/
|
||||||
public WebTestClient.ResponseSpec isNoContent() {
|
public WebTestClient.ResponseSpec isNoContent() {
|
||||||
assertEquals("Status", HttpStatus.NO_CONTENT, this.httpStatus);
|
assertEquals("Status", HttpStatus.NO_CONTENT, getStatus());
|
||||||
return this.responseSpec;
|
return this.responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,7 +92,7 @@ public class StatusAssertions {
|
||||||
* Assert the response status code is {@code HttpStatus.FOUND} (302).
|
* Assert the response status code is {@code HttpStatus.FOUND} (302).
|
||||||
*/
|
*/
|
||||||
public WebTestClient.ResponseSpec isFound() {
|
public WebTestClient.ResponseSpec isFound() {
|
||||||
assertEquals("Status", HttpStatus.FOUND, this.httpStatus);
|
assertEquals("Status", HttpStatus.FOUND, getStatus());
|
||||||
return this.responseSpec;
|
return this.responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,7 +100,7 @@ public class StatusAssertions {
|
||||||
* Assert the response status code is {@code HttpStatus.SEE_OTHER} (303).
|
* Assert the response status code is {@code HttpStatus.SEE_OTHER} (303).
|
||||||
*/
|
*/
|
||||||
public WebTestClient.ResponseSpec isSeeOther() {
|
public WebTestClient.ResponseSpec isSeeOther() {
|
||||||
assertEquals("Status", HttpStatus.SEE_OTHER, this.httpStatus);
|
assertEquals("Status", HttpStatus.SEE_OTHER, getStatus());
|
||||||
return this.responseSpec;
|
return this.responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,7 +108,7 @@ public class StatusAssertions {
|
||||||
* Assert the response status code is {@code HttpStatus.NOT_MODIFIED} (304).
|
* Assert the response status code is {@code HttpStatus.NOT_MODIFIED} (304).
|
||||||
*/
|
*/
|
||||||
public WebTestClient.ResponseSpec isNotModified() {
|
public WebTestClient.ResponseSpec isNotModified() {
|
||||||
assertEquals("Status", HttpStatus.NOT_MODIFIED, this.httpStatus);
|
assertEquals("Status", HttpStatus.NOT_MODIFIED, getStatus());
|
||||||
return this.responseSpec;
|
return this.responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,7 +116,7 @@ public class StatusAssertions {
|
||||||
* Assert the response status code is {@code HttpStatus.TEMPORARY_REDIRECT} (307).
|
* Assert the response status code is {@code HttpStatus.TEMPORARY_REDIRECT} (307).
|
||||||
*/
|
*/
|
||||||
public WebTestClient.ResponseSpec isTemporaryRedirect() {
|
public WebTestClient.ResponseSpec isTemporaryRedirect() {
|
||||||
assertEquals("Status", HttpStatus.TEMPORARY_REDIRECT, this.httpStatus);
|
assertEquals("Status", HttpStatus.TEMPORARY_REDIRECT, getStatus());
|
||||||
return this.responseSpec;
|
return this.responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -124,7 +124,7 @@ public class StatusAssertions {
|
||||||
* Assert the response status code is {@code HttpStatus.PERMANENT_REDIRECT} (308).
|
* Assert the response status code is {@code HttpStatus.PERMANENT_REDIRECT} (308).
|
||||||
*/
|
*/
|
||||||
public WebTestClient.ResponseSpec isPermanentRedirect() {
|
public WebTestClient.ResponseSpec isPermanentRedirect() {
|
||||||
assertEquals("Status", HttpStatus.PERMANENT_REDIRECT, this.httpStatus);
|
assertEquals("Status", HttpStatus.PERMANENT_REDIRECT, getStatus());
|
||||||
return this.responseSpec;
|
return this.responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -132,7 +132,15 @@ public class StatusAssertions {
|
||||||
* Assert the response status code is {@code HttpStatus.BAD_REQUEST} (400).
|
* Assert the response status code is {@code HttpStatus.BAD_REQUEST} (400).
|
||||||
*/
|
*/
|
||||||
public WebTestClient.ResponseSpec isBadRequest() {
|
public WebTestClient.ResponseSpec isBadRequest() {
|
||||||
assertEquals("Status", HttpStatus.BAD_REQUEST, this.httpStatus);
|
assertEquals("Status", HttpStatus.BAD_REQUEST, getStatus());
|
||||||
|
return this.responseSpec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert the response status code is {@code HttpStatus.UNAUTHORIZED} (401).
|
||||||
|
*/
|
||||||
|
public WebTestClient.ResponseSpec isUnauthorized() {
|
||||||
|
assertEquals("Status", HttpStatus.UNAUTHORIZED, getStatus());
|
||||||
return this.responseSpec;
|
return this.responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,7 +148,7 @@ public class StatusAssertions {
|
||||||
* Assert the response status code is {@code HttpStatus.NOT_FOUND} (404).
|
* Assert the response status code is {@code HttpStatus.NOT_FOUND} (404).
|
||||||
*/
|
*/
|
||||||
public WebTestClient.ResponseSpec isNotFound() {
|
public WebTestClient.ResponseSpec isNotFound() {
|
||||||
assertEquals("Status", HttpStatus.NOT_FOUND, this.httpStatus);
|
assertEquals("Status", HttpStatus.NOT_FOUND, getStatus());
|
||||||
return this.responseSpec;
|
return this.responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,7 +156,7 @@ public class StatusAssertions {
|
||||||
* Assert the response error message.
|
* Assert the response error message.
|
||||||
*/
|
*/
|
||||||
public WebTestClient.ResponseSpec reasonEquals(String reason) {
|
public WebTestClient.ResponseSpec reasonEquals(String reason) {
|
||||||
assertEquals("Response status reason", reason, this.httpStatus.getReasonPhrase());
|
assertEquals("Response status reason", reason, getStatus().getReasonPhrase());
|
||||||
return this.responseSpec;
|
return this.responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -156,8 +164,8 @@ public class StatusAssertions {
|
||||||
* Assert the response status code is in the 1xx range.
|
* Assert the response status code is in the 1xx range.
|
||||||
*/
|
*/
|
||||||
public WebTestClient.ResponseSpec is1xxInformational() {
|
public WebTestClient.ResponseSpec is1xxInformational() {
|
||||||
String message = "Range for response status value " + this.httpStatus;
|
String message = "Range for response status value " + getStatus();
|
||||||
assertEquals(message, HttpStatus.Series.INFORMATIONAL, this.httpStatus.series());
|
assertEquals(message, HttpStatus.Series.INFORMATIONAL, getStatus().series());
|
||||||
return this.responseSpec;
|
return this.responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,8 +173,8 @@ public class StatusAssertions {
|
||||||
* Assert the response status code is in the 2xx range.
|
* Assert the response status code is in the 2xx range.
|
||||||
*/
|
*/
|
||||||
public WebTestClient.ResponseSpec is2xxSuccessful() {
|
public WebTestClient.ResponseSpec is2xxSuccessful() {
|
||||||
String message = "Range for response status value " + this.httpStatus;
|
String message = "Range for response status value " + getStatus();
|
||||||
assertEquals(message, HttpStatus.Series.SUCCESSFUL, this.httpStatus.series());
|
assertEquals(message, HttpStatus.Series.SUCCESSFUL, getStatus().series());
|
||||||
return this.responseSpec;
|
return this.responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -174,8 +182,8 @@ public class StatusAssertions {
|
||||||
* Assert the response status code is in the 3xx range.
|
* Assert the response status code is in the 3xx range.
|
||||||
*/
|
*/
|
||||||
public WebTestClient.ResponseSpec is3xxRedirection() {
|
public WebTestClient.ResponseSpec is3xxRedirection() {
|
||||||
String message = "Range for response status value " + this.httpStatus;
|
String message = "Range for response status value " + getStatus();
|
||||||
assertEquals(message, HttpStatus.Series.REDIRECTION, this.httpStatus.series());
|
assertEquals(message, HttpStatus.Series.REDIRECTION, getStatus().series());
|
||||||
return this.responseSpec;
|
return this.responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -183,8 +191,8 @@ public class StatusAssertions {
|
||||||
* Assert the response status code is in the 4xx range.
|
* Assert the response status code is in the 4xx range.
|
||||||
*/
|
*/
|
||||||
public WebTestClient.ResponseSpec is4xxClientError() {
|
public WebTestClient.ResponseSpec is4xxClientError() {
|
||||||
String message = "Range for response status value " + this.httpStatus;
|
String message = "Range for response status value " + getStatus();
|
||||||
assertEquals(message, HttpStatus.Series.CLIENT_ERROR, this.httpStatus.series());
|
assertEquals(message, HttpStatus.Series.CLIENT_ERROR, getStatus().series());
|
||||||
return this.responseSpec;
|
return this.responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,9 +200,16 @@ public class StatusAssertions {
|
||||||
* Assert the response status code is in the 5xx range.
|
* Assert the response status code is in the 5xx range.
|
||||||
*/
|
*/
|
||||||
public WebTestClient.ResponseSpec is5xxServerError() {
|
public WebTestClient.ResponseSpec is5xxServerError() {
|
||||||
String message = "Range for response status value " + this.httpStatus;
|
String message = "Range for response status value " + getStatus();
|
||||||
assertEquals(message, HttpStatus.Series.SERVER_ERROR, this.httpStatus.series());
|
assertEquals(message, HttpStatus.Series.SERVER_ERROR, getStatus().series());
|
||||||
return this.responseSpec;
|
return this.responseSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Private methods
|
||||||
|
|
||||||
|
private HttpStatus getStatus() {
|
||||||
|
return this.exchangeResult.getStatus();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -448,7 +448,7 @@ public interface WebTestClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specification for expectations on the response.
|
* Specification for processing the response and applying expectations.
|
||||||
*/
|
*/
|
||||||
interface ResponseSpec {
|
interface ResponseSpec {
|
||||||
|
|
||||||
|
|
@ -458,40 +458,123 @@ public interface WebTestClient {
|
||||||
StatusAssertions expectStatus();
|
StatusAssertions expectStatus();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assertions on the response headers.
|
* Assertions on the headers of the response.
|
||||||
*/
|
*/
|
||||||
HeaderAssertions expectHeader();
|
HeaderAssertions expectHeader();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assertions on the response body.
|
* Assertions on the body of the response extracted to one or more
|
||||||
|
* elements of the given type.
|
||||||
|
*/
|
||||||
|
TypeBodySpec expectBody(Class<?> elementType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variant of {@link #expectBody(Class)} for use with generic types.
|
||||||
|
*/
|
||||||
|
TypeBodySpec expectBody(ResolvableType elementType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access to additional assertions on the response body --
|
||||||
|
* isEmpty, map, and others.
|
||||||
*/
|
*/
|
||||||
BodySpec expectBody();
|
BodySpec expectBody();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assertions on the response body where the body is to be decoded to
|
* Consume the {@link ExchangeResult} and continue with expectations.
|
||||||
* one or more elements of the given type.
|
* The {@code ExchangeResult} is parameterized with data buffers since
|
||||||
*/
|
* the body is not yet consumed nor decoded at this level.
|
||||||
ElementBodySpec expectBody(Class<?> elementType);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alternative to {@link #expectBody(Class)} for generic types.
|
|
||||||
*/
|
|
||||||
ElementBodySpec expectBody(ResolvableType elementType);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Consume the result of the exchange and continue with expectations.
|
|
||||||
*/
|
*/
|
||||||
ResponseSpec consumeWith(Consumer<ExchangeResult<Flux<DataBuffer>>> consumer);
|
ResponseSpec consumeWith(Consumer<ExchangeResult<Flux<DataBuffer>>> consumer);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a container for the result of the exchange with the body
|
* Return a container for the result of the exchange. The returned
|
||||||
* not yet decoded nor consumed.
|
* {@code ExchangeResult} is parameterized with data buffers since
|
||||||
|
* the body is not yet consumed nor decoded at this level.
|
||||||
*/
|
*/
|
||||||
ExchangeResult<Flux<DataBuffer>> returnResult();
|
ExchangeResult<Flux<DataBuffer>> returnResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specification for expectations on the body of the response.
|
* Specification for extracting entities from the response body.
|
||||||
|
*/
|
||||||
|
interface TypeBodySpec {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract a single value from the response.
|
||||||
|
*/
|
||||||
|
SingleValueBodySpec value();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract a list of values from the response.
|
||||||
|
*/
|
||||||
|
ListBodySpec list();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract a list of values consuming the first N elements.
|
||||||
|
*/
|
||||||
|
ListBodySpec list(int elementCount);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a container for the result of the exchange parameterized with
|
||||||
|
* the {@code Flux} of decoded objects (not yet consumed).
|
||||||
|
*/
|
||||||
|
<T> ExchangeResult<Flux<T>> returnResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specification to assert a single value extracted from the response body.
|
||||||
|
*/
|
||||||
|
interface SingleValueBodySpec {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert the extracted body is equal to the given value.
|
||||||
|
*/
|
||||||
|
<T> ExchangeResult<T> isEqualTo(Object expected);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a container for the result of the exchange parameterized with
|
||||||
|
* the extracted response entity.
|
||||||
|
*/
|
||||||
|
<T> ExchangeResult<T> returnResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specification to assert a list of values extracted from the response.
|
||||||
|
*/
|
||||||
|
interface ListBodySpec {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert the extracted body is equal to the given list.
|
||||||
|
*/
|
||||||
|
<T> ExchangeResult<List<T>> isEqualTo(List<T> expected);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert the extracted list of values is of the given size.
|
||||||
|
* @param size the expected size
|
||||||
|
*/
|
||||||
|
ListBodySpec hasSize(int size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert the extracted list of values contains the given elements.
|
||||||
|
* @param elements the elements to check
|
||||||
|
*/
|
||||||
|
ListBodySpec contains(Object... elements);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert the extracted list of values doesn't contain the given elements.
|
||||||
|
* @param elements the elements to check
|
||||||
|
*/
|
||||||
|
ListBodySpec doesNotContain(Object... elements);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a container for the result of the exchange parameterized with
|
||||||
|
* the extracted list of response entities.
|
||||||
|
*/
|
||||||
|
<T> ExchangeResult<List<T>> returnResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specification to apply additional assertions on the response body.
|
||||||
*/
|
*/
|
||||||
interface BodySpec {
|
interface BodySpec {
|
||||||
|
|
||||||
|
|
@ -502,48 +585,48 @@ public interface WebTestClient {
|
||||||
ExchangeResult<Void> isEmpty();
|
ExchangeResult<Void> isEmpty();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode the response body as a Map with the given key and value type.
|
* Extract the response body as a Map with the given key and value type.
|
||||||
*/
|
*/
|
||||||
MapBodySpec map(Class<?> keyType, Class<?> valueType);
|
MapBodySpec map(Class<?> keyType, Class<?> valueType);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alternative to {@link #map(Class, Class)} for generic types.
|
* Variant of {@link #map(Class, Class)} for use with generic types.
|
||||||
*/
|
*/
|
||||||
MapBodySpec map(ResolvableType keyType, ResolvableType valueType);
|
MapBodySpec map(ResolvableType keyType, ResolvableType valueType);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specification for expectations on the body of the response decoded as a map.
|
* Specification to assert response the body extracted as a map.
|
||||||
*/
|
*/
|
||||||
interface MapBodySpec {
|
interface MapBodySpec {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assert the decoded body is equal to the given list of elements.
|
* Assert the extracted map is equal to the given list of elements.
|
||||||
*/
|
*/
|
||||||
<K, V> ExchangeResult<Map<K, V>> isEqualTo(Map<K, V> expected);
|
<K, V> ExchangeResult<Map<K, V>> isEqualTo(Map<K, V> expected);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assert the decoded map has the given size.
|
* Assert the extracted map has the given size.
|
||||||
* @param size the expected size
|
* @param size the expected size
|
||||||
*/
|
*/
|
||||||
MapBodySpec hasSize(int size);
|
MapBodySpec hasSize(int size);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assert the decoded map contains the given key value pair.
|
* Assert the extracted map contains the given key value pair.
|
||||||
* @param key the key to check
|
* @param key the key to check
|
||||||
* @param value the value to check
|
* @param value the value to check
|
||||||
*/
|
*/
|
||||||
MapBodySpec contains(Object key, Object value);
|
MapBodySpec contains(Object key, Object value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assert the decoded map contains the given keys.
|
* Assert the extracted map contains the given keys.
|
||||||
* @param keys the keys to check
|
* @param keys the keys to check
|
||||||
*/
|
*/
|
||||||
MapBodySpec containsKeys(Object... keys);
|
MapBodySpec containsKeys(Object... keys);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assert the decoded map contains the given values.
|
* Assert the extracted map contains the given values.
|
||||||
* @param values the keys to check
|
* @param values the keys to check
|
||||||
*/
|
*/
|
||||||
MapBodySpec containsValues(Object... values);
|
MapBodySpec containsValues(Object... values);
|
||||||
|
|
@ -554,85 +637,4 @@ public interface WebTestClient {
|
||||||
<K, V> ExchangeResult<Map<K, V>> returnResult();
|
<K, V> ExchangeResult<Map<K, V>> returnResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Specification for expectations on the body of the response to be decoded
|
|
||||||
* as one or more elements of a specific type.
|
|
||||||
*/
|
|
||||||
interface ElementBodySpec {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode the response as a single element.
|
|
||||||
*/
|
|
||||||
SingleValueBodySpec value();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode the response as a Flux of objects and collect it to a list.
|
|
||||||
*/
|
|
||||||
ListBodySpec list();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode the response as a Flux of objects consuming only the specified
|
|
||||||
* number of elements.
|
|
||||||
*/
|
|
||||||
ListBodySpec list(int elementCount);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode the response as a Flux of objects and return a container for
|
|
||||||
* the result where the response body not yet consumed.
|
|
||||||
*/
|
|
||||||
<T> ExchangeResult<Flux<T>> returnResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specification for expectations on the body of the response decoded as a
|
|
||||||
* single element of a specific type.
|
|
||||||
*/
|
|
||||||
interface SingleValueBodySpec {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assert the decoded body is equal to the given value.
|
|
||||||
*/
|
|
||||||
<T> ExchangeResult<T> isEqualTo(Object expected);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a container for the result of the exchange.
|
|
||||||
*/
|
|
||||||
<T> ExchangeResult<T> returnResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specification for expectations on the body of the response decoded as a
|
|
||||||
* list of elements of a specific type.
|
|
||||||
*/
|
|
||||||
interface ListBodySpec {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assert the decoded body is equal to the given list of elements.
|
|
||||||
*/
|
|
||||||
<T> ExchangeResult<List<T>> isEqualTo(List<T> expected);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assert the decoded list of values is of the given size.
|
|
||||||
* @param size the expected size
|
|
||||||
*/
|
|
||||||
ListBodySpec hasSize(int size);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assert the decoded list of values contains the given elements.
|
|
||||||
* @param elements the elements to check
|
|
||||||
*/
|
|
||||||
ListBodySpec contains(Object... elements);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assert the decoded list of values does not contain the given elements.
|
|
||||||
* @param elements the elements to check
|
|
||||||
*/
|
|
||||||
ListBodySpec doesNotContain(Object... elements);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a container for the result of the exchange.
|
|
||||||
*/
|
|
||||||
<T> ExchangeResult<List<T>> returnResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
* 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.test.web.reactive.server;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||||
|
import org.springframework.http.client.reactive.ClientHttpRequest;
|
||||||
|
import org.springframework.http.client.reactive.ClientHttpResponse;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorate any other {@link ClientHttpConnector} with the purpose of
|
||||||
|
* intercepting, capturing, and exposing {@code ClientHttpRequest}s reflecting
|
||||||
|
* the exact and complete details sent to the server.
|
||||||
|
*
|
||||||
|
* @author Rossen Stoyanchev
|
||||||
|
* @since 5.0
|
||||||
|
* @see HttpHandlerConnector
|
||||||
|
*/
|
||||||
|
class WebTestClientConnector implements ClientHttpConnector {
|
||||||
|
|
||||||
|
public static final String REQUEST_ID_HEADER_NAME = "request-id";
|
||||||
|
|
||||||
|
|
||||||
|
private final ClientHttpConnector delegate;
|
||||||
|
|
||||||
|
private final Map<String, ClientHttpRequest> capturedRequests = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
public WebTestClientConnector(ClientHttpConnector delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<ClientHttpResponse> connect(HttpMethod method, URI uri,
|
||||||
|
Function<? super ClientHttpRequest, Mono<Void>> requestCallback) {
|
||||||
|
|
||||||
|
AtomicReference<ClientHttpRequest> requestRef = new AtomicReference<>();
|
||||||
|
|
||||||
|
return this.delegate
|
||||||
|
.connect(method, uri, request -> {
|
||||||
|
requestRef.set(request);
|
||||||
|
return requestCallback.apply(request);
|
||||||
|
})
|
||||||
|
.doOnNext(response -> {
|
||||||
|
ClientHttpRequest request = requestRef.get();
|
||||||
|
String id = request.getHeaders().getFirst(REQUEST_ID_HEADER_NAME);
|
||||||
|
if (id != null) {
|
||||||
|
this.capturedRequests.put(id, request);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the request with the given "request-id" header.
|
||||||
|
*/
|
||||||
|
public ClientHttpRequest claimRequest(String requestId) {
|
||||||
|
ClientHttpRequest request = this.capturedRequests.get(requestId);
|
||||||
|
Assert.notNull(request, "No matching request [" + requestId + "]. Did connect return a response yet?");
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,117 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.test.web.reactive.server;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
|
||||||
import org.springframework.http.client.reactive.ClientHttpRequest;
|
|
||||||
import org.springframework.http.client.reactive.ClientHttpResponse;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decorates a {@link ClientHttpConnector} in order to capture executed requests
|
|
||||||
* and responses and notify one or more registered listeners. This is helpful
|
|
||||||
* for access to the actual {@link ClientHttpRequest} sent and the
|
|
||||||
* {@link ClientHttpResponse} returned by the server.
|
|
||||||
*
|
|
||||||
* @author Rossen Stoyanchev
|
|
||||||
* @since 5.0
|
|
||||||
*/
|
|
||||||
class WiretapConnector implements ClientHttpConnector {
|
|
||||||
|
|
||||||
private final ClientHttpConnector delegate;
|
|
||||||
|
|
||||||
private final List<Consumer<Info>> listeners;
|
|
||||||
|
|
||||||
|
|
||||||
public WiretapConnector(ClientHttpConnector delegate) {
|
|
||||||
this.delegate = delegate;
|
|
||||||
this.listeners = new CopyOnWriteArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a listener to consume exchanged requests and responses.
|
|
||||||
*/
|
|
||||||
public void addListener(Consumer<Info> consumer) {
|
|
||||||
this.listeners.add(consumer);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Mono<ClientHttpResponse> connect(HttpMethod method, URI uri,
|
|
||||||
Function<? super ClientHttpRequest, Mono<Void>> requestCallback) {
|
|
||||||
|
|
||||||
AtomicReference<ClientHttpRequest> requestRef = new AtomicReference<>();
|
|
||||||
|
|
||||||
return this.delegate
|
|
||||||
.connect(method, uri, request -> {
|
|
||||||
requestRef.set(request);
|
|
||||||
return requestCallback.apply(request);
|
|
||||||
})
|
|
||||||
.doOnNext(response -> {
|
|
||||||
Info info = new Info(requestRef.get(), response);
|
|
||||||
this.listeners.forEach(consumer -> consumer.accept(info));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static class Info {
|
|
||||||
|
|
||||||
private final HttpMethod method;
|
|
||||||
|
|
||||||
private final URI url;
|
|
||||||
|
|
||||||
private final HttpHeaders requestHeaders;
|
|
||||||
|
|
||||||
private final ClientHttpResponse response;
|
|
||||||
|
|
||||||
|
|
||||||
public Info(ClientHttpRequest request, ClientHttpResponse response) {
|
|
||||||
this.method = request.getMethod();
|
|
||||||
this.url = request.getURI();
|
|
||||||
this.requestHeaders = request.getHeaders();
|
|
||||||
this.response = response;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public HttpMethod getMethod() {
|
|
||||||
return this.method;
|
|
||||||
}
|
|
||||||
|
|
||||||
public URI getUrl() {
|
|
||||||
return this.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpHeaders getRequestHeaders() {
|
|
||||||
return this.requestHeaders;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientHttpResponse getResponse() {
|
|
||||||
return this.response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
package org.springframework.test.web.reactive.server;
|
package org.springframework.test.web.reactive.server;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
@ -35,35 +34,32 @@ import org.springframework.web.reactive.function.client.ExchangeFunctions;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertSame;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for {@link WiretapConnector}.
|
* Unit tests for {@link WebTestClientConnector}.
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
*/
|
*/
|
||||||
public class WiretapConnectorTests {
|
public class WebTestClientConnectorTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void listener() throws Exception {
|
public void captureAndClaim() throws Exception {
|
||||||
|
|
||||||
ClientHttpRequest request = new MockClientHttpRequest(HttpMethod.GET, "/test");
|
ClientHttpRequest request = new MockClientHttpRequest(HttpMethod.GET, "/test");
|
||||||
ClientHttpResponse response = new MockClientHttpResponse(HttpStatus.OK);
|
ClientHttpResponse response = new MockClientHttpResponse(HttpStatus.OK);
|
||||||
ClientHttpConnector connector = (method, uri, fn) -> fn.apply(request).then(Mono.just(response));
|
ClientHttpConnector connector = (method, uri, fn) -> fn.apply(request).then(Mono.just(response));
|
||||||
|
|
||||||
AtomicReference<WiretapConnector.Info> infoRef = new AtomicReference<>();
|
ClientRequest clientRequest = ClientRequest.method(HttpMethod.GET, URI.create("/test"))
|
||||||
WiretapConnector wiretapConnector = new WiretapConnector(connector);
|
.header(WebTestClientConnector.REQUEST_ID_HEADER_NAME, "1").build();
|
||||||
wiretapConnector.addListener(infoRef::set);
|
|
||||||
|
|
||||||
ExchangeFunction exchangeFn = ExchangeFunctions.create(wiretapConnector);
|
WebTestClientConnector webTestClientConnector = new WebTestClientConnector(connector);
|
||||||
ClientRequest clientRequest = ClientRequest.method(HttpMethod.GET, URI.create("/test")).build();
|
ExchangeFunction function = ExchangeFunctions.create(webTestClientConnector);
|
||||||
exchangeFn.exchange(clientRequest).blockMillis(0);
|
function.exchange(clientRequest).blockMillis(0);
|
||||||
|
|
||||||
WiretapConnector.Info info = infoRef.get();
|
ClientHttpRequest actual = webTestClientConnector.claimRequest("1");
|
||||||
assertNotNull(info);
|
assertNotNull(actual);
|
||||||
assertEquals(HttpMethod.GET, info.getMethod());
|
assertEquals(HttpMethod.GET, actual.getMethod());
|
||||||
assertEquals("/test", info.getUrl().toString());
|
assertEquals("/test", actual.getURI().toString());
|
||||||
assertSame(response, info.getResponse());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -66,7 +66,7 @@ public class ResponseEntityTests {
|
||||||
this.client.get().uri("/persons/John")
|
this.client.get().uri("/persons/John")
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus().isOk()
|
.expectStatus().isOk()
|
||||||
.expectHeader().contentTypeEquals(MediaType.APPLICATION_JSON_UTF8)
|
.expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
|
||||||
.expectBody(Person.class).value().isEqualTo(new Person("John"));
|
.expectBody(Person.class).value().isEqualTo(new Person("John"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,7 +79,7 @@ public class ResponseEntityTests {
|
||||||
this.client.get().uri("/persons")
|
this.client.get().uri("/persons")
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus().isOk()
|
.expectStatus().isOk()
|
||||||
.expectHeader().contentTypeEquals(MediaType.APPLICATION_JSON_UTF8)
|
.expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
|
||||||
.expectBody(Person.class).list().isEqualTo(expected);
|
.expectBody(Person.class).list().isEqualTo(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,7 +106,7 @@ public class ResponseEntityTests {
|
||||||
.accept(TEXT_EVENT_STREAM)
|
.accept(TEXT_EVENT_STREAM)
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus().isOk()
|
.expectStatus().isOk()
|
||||||
.expectHeader().contentTypeEquals(TEXT_EVENT_STREAM)
|
.expectHeader().contentType(TEXT_EVENT_STREAM)
|
||||||
.expectBody(Person.class)
|
.expectBody(Person.class)
|
||||||
.returnResult();
|
.returnResult();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue