diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java index f1d7b6f191..5535cadc2f 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java @@ -19,6 +19,7 @@ import java.net.URI; import java.nio.charset.Charset; import java.time.Duration; import java.time.ZonedDateTime; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; @@ -27,12 +28,16 @@ import java.util.function.Consumer; import java.util.function.Function; import org.reactivestreams.Publisher; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import org.springframework.core.ResolvableType; +import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.client.reactive.ClientHttpConnector; import org.springframework.http.client.reactive.ClientHttpRequest; +import org.springframework.test.util.AssertionErrors; import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; import org.springframework.web.reactive.function.BodyInserter; @@ -41,6 +46,10 @@ import org.springframework.web.reactive.function.client.ExchangeFilterFunction; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.util.UriBuilder; +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.toMono; + /** * Default implementation of {@link WebTestClient}. * @@ -231,35 +240,91 @@ class DefaultWebTestClient implements WebTestClient { return this; } - @Override - public ExchangeActions exchange() { - return getExchangeActions(this.headerSpec.exchange()); + public ResponseSpec exchange() { + return new DefaultResponseSpec(this.requestId, this.headerSpec.exchange()); } @Override - public ExchangeActions exchange(BodyInserter inserter) { - return getExchangeActions(this.headerSpec.exchange(inserter)); + public ResponseSpec exchange(BodyInserter inserter) { + return new DefaultResponseSpec(this.requestId, this.headerSpec.exchange(inserter)); } @Override - public > ExchangeActions exchange(S publisher, Class elementClass) { - return getExchangeActions(this.headerSpec.exchange(publisher, elementClass)); + public > ResponseSpec exchange(S publisher, Class elementClass) { + return new DefaultResponseSpec(this.requestId, this.headerSpec.exchange(publisher, elementClass)); + } + } + + private class DefaultResponseSpec implements ResponseSpec { + + private final String requestId; + + private final Mono responseMono; + + + public DefaultResponseSpec(String requestId, Mono responseMono) { + this.requestId = requestId; + this.responseMono = responseMono; } - private ExchangeActions getExchangeActions(Mono responseMono) { - ClientResponse response = responseMono.block(getTimeout()); - ExchangeInfo info = getExchangeInfo(response); - return new ExchangeActions(info); + + @Override + public ExchangeResult decodeEntity(Class entityClass) { + return decodeEntity(ResolvableType.forClass(entityClass)); } - private ExchangeInfo getExchangeInfo(ClientResponse clientResponse) { - WiretapConnector.Info wiretapInfo = connectorListener.retrieveRequest(this.requestId); + @Override + public ExchangeResult> decodeAndCollect(Class elementClass) { + return decodeAndCollect(ResolvableType.forClass(elementClass)); + } + + @Override + public ExchangeResult> decodeFlux(Class elementClass) { + return decodeFlux(ResolvableType.forClass(elementClass)); + } + + @Override + public ExchangeResult decodeEntity(ResolvableType elementType) { + return this.responseMono.then(response -> { + Mono entityMono = response.body(toMono(elementType)); + return entityMono.map(entity -> createTestExchange(entity, response)); + }).block(getTimeout()); + } + + @Override + public ExchangeResult> decodeAndCollect(ResolvableType elementType) { + return this.responseMono.then(response -> { + Flux entityFlux = response.body(toFlux(elementType)); + return entityFlux.collectList().map(list -> createTestExchange(list, response)); + }).block(getTimeout()); + } + + @Override + public ExchangeResult> decodeFlux(ResolvableType elementType) { + return this.responseMono.map(response -> { + Flux entityFlux = response.body(toFlux(elementType)); + return createTestExchange(entityFlux, response); + }).block(getTimeout()); + } + + @Override + public ExchangeResult expectNoBody() { + return this.responseMono.map(response -> { + DataBuffer buffer = response.body(toDataBuffers()).blockFirst(getTimeout()); + AssertionErrors.assertTrue("Expected empty body", buffer == null); + ExchangeResult exchange = createTestExchange(null, response); + return exchange; + }).block(getTimeout()); + } + + private ExchangeResult createTestExchange(T body, ClientResponse response) { + WiretapConnector.Info wiretapInfo = connectorListener.retrieveRequest(requestId); ClientHttpRequest request = wiretapInfo.getRequest(); - return new ExchangeInfo(request.getMethod(), request.getURI(), request.getHeaders(), - clientResponse, getTimeout()); + return new ExchangeResult( + request.getMethod(), request.getURI(), request.getHeaders(), + response.statusCode(), response.headers().asHttpHeaders(), body); } - } @@ -287,7 +352,7 @@ class DefaultWebTestClient implements WebTestClient { public WiretapConnector.Info retrieveRequest(String requestId) { WiretapConnector.Info info = this.exchanges.remove(requestId); - Assert.notNull(info, "No match for \"request-id\"=" + requestId); + Assert.notNull(info, "No match for request-id=" + requestId); return info; } } diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ExchangeActions.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/ExchangeActions.java deleted file mode 100644 index 84e79b0ce2..0000000000 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ExchangeActions.java +++ /dev/null @@ -1,98 +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.util.Collections; -import java.util.List; - -import org.springframework.core.ResolvableType; -import org.springframework.http.HttpHeaders; - -/** - * Entry point for applying assertions and actions on a performed exchange. - * - * @author Rossen Stoyanchev - * @since 5.0 - */ -public final class ExchangeActions { - - private final ExchangeInfo exchangeInfo; - - - public ExchangeActions(ExchangeInfo info) { - this.exchangeInfo = info; - } - - - /** - * Assert the status of the response. - * @return further options for asserting the status of the response - */ - public ResponseStatusAssertions assertStatus() { - return new ResponseStatusAssertions(this); - } - - /** - * Assert specific, commonly used response headers. - * @return further options for asserting headers - */ - public ResponseHeadersAssertions assertHeaders() { - HttpHeaders headers = this.exchangeInfo.getResponse().headers().asHttpHeaders(); - return new ResponseHeadersAssertions(this, headers); - } - - /** - * Assert value(s) of the specified header name. - * @param headerName the header name - * @return options for asserting the header value(s) - */ - public ResponseHeaderAssertions assertHeader(String headerName) { - HttpHeaders headers = this.exchangeInfo.getResponse().headers().asHttpHeaders(); - List values = headers.getOrDefault(headerName, Collections.emptyList()); - return new ResponseHeaderAssertions(this, headerName, values); - } - - /** - * Assertions on the body of the response decoded as one or more response - * entities of the given type. - * @return options for asserting response entities - */ - public ResponseEntityAssertions assertEntity(Class entityType) { - return new ResponseEntityAssertions(this, ResolvableType.forClass(entityType)); - } - - /** - * Assertions on the body of the response. - */ - public ResponseBodyAssertions assertBody() { - return new ResponseBodyAssertions(this); - } - - /** - * Log debug information about the exchange. - */ - public LoggingExchangeConsumer log() { - return new LoggingExchangeConsumer(this); - } - - /** - * Return {@link ExchangeInfo} for direct access to request and response. - */ - public ExchangeInfo andReturn() { - return this.exchangeInfo; - } - -} diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ExchangeInfo.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/ExchangeResult.java similarity index 52% rename from spring-test/src/main/java/org/springframework/test/web/reactive/server/ExchangeInfo.java rename to spring-test/src/main/java/org/springframework/test/web/reactive/server/ExchangeResult.java index a33d211acf..72097b2d32 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ExchangeInfo.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/ExchangeResult.java @@ -16,21 +16,28 @@ package org.springframework.test.web.reactive.server; import java.net.URI; -import java.time.Duration; import java.util.stream.Collectors; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; -import org.springframework.web.reactive.function.client.ClientResponse; /** - * Contains information about a performed exchange. + * Container for request and response details including the decoded response + * body from an exchange performed through {@link WebTestClient}. + * + *

Use {@link #assertThat()} to access built-in assertions on the response, + * or apply other assertions directly to the data contained in this class. + * The built-in assertions provide an option for logging diagnostic information + * about the exchange. The same can also be obtained using the + * {@link #toString()} method of this class. + * + * @param the type of the decoded response body * * @author Rossen Stoyanchev * @since 5.0 */ -public class ExchangeInfo { +public class ExchangeResult { private final HttpMethod method; @@ -38,33 +45,44 @@ public class ExchangeInfo { private final HttpHeaders requestHeaders; - private final ClientResponse response; + private final HttpStatus status; - private final Duration responseTimeout; + private final HttpHeaders responseHeaders; + + private final T responseBody; - public ExchangeInfo(HttpMethod httpMethod, URI uri, HttpHeaders requestHeaders, - ClientResponse response, Duration responseTimeout) { + public ExchangeResult(HttpMethod method, URI url, HttpHeaders requestHeaders, + HttpStatus status, HttpHeaders responseHeaders, T responseBody) { - this.method = httpMethod; - this.url = uri; + this.method = method; + this.url = url; this.requestHeaders = requestHeaders; - this.response = response; - this.responseTimeout = responseTimeout; + this.status = status; + this.responseHeaders = responseHeaders; + this.responseBody = responseBody; } /** - * Return the HTTP method of the exchange. + * Provides access to built-in assertions on the response. */ - public HttpMethod getHttpMethod() { + public ResponseAssertions assertThat() { + return new ResponseAssertions(this); + } + + + /** + * Return the request method of the exchange. + */ + public HttpMethod getRequestMethod() { return this.method; } /** - * Return the URI of the exchange. + * Return the URL of the exchange. */ - public URI getUrl() { + public URI getRequestUrl() { return this.url; } @@ -76,30 +94,37 @@ public class ExchangeInfo { } /** - * Return the {@link ClientResponse} for the exchange. + * Return the response status. */ - public ClientResponse getResponse() { - return this.response; + public HttpStatus getResponseStatus() { + return this.status; } /** - * Return the configured timeout for blocking on response data. + * Return the response headers. */ - public Duration getResponseTimeout() { - return this.responseTimeout; + public HttpHeaders getResponseHeaders() { + return this.responseHeaders; + } + + /** + * Return the decoded response body. + */ + public T getResponseBody() { + return this.responseBody; } @Override public String toString() { - HttpStatus status = getResponse().statusCode(); + HttpStatus status = this.status; return "\n\n" + - formatValue("Request", getHttpMethod() + " " + getUrl()) + + formatValue("Request", this.method + " " + getRequestUrl()) + formatValue("Status", status + " " + status.getReasonPhrase()) + formatHeading("Response Headers") + - formatHeaders(getResponse().headers().asHttpHeaders()) + + formatHeaders(this.responseHeaders) + formatHeading("Request Headers") + - formatHeaders(getRequestHeaders()); + formatHeaders(this.requestHeaders); } private String formatHeading(String heading) { diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ResponseHeadersAssertions.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/HeaderAssertions.java similarity index 50% rename from spring-test/src/main/java/org/springframework/test/web/reactive/server/ResponseHeadersAssertions.java rename to spring-test/src/main/java/org/springframework/test/web/reactive/server/HeaderAssertions.java index 94b761163c..be32eed1b1 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ResponseHeadersAssertions.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/HeaderAssertions.java @@ -15,78 +15,88 @@ */ package org.springframework.test.web.reactive.server; -import java.util.function.Consumer; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; import org.springframework.http.CacheControl; import org.springframework.http.ContentDisposition; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; +import org.springframework.util.CollectionUtils; import static org.springframework.test.util.AssertionErrors.assertEquals; +import static org.springframework.test.util.AssertionErrors.assertTrue; /** - * Assertions on specific, commonly used response headers. + * Provides methods for HTTP header assertions. * * @author Rossen Stoyanchev * @since 5.0 + * @see ResponseAssertions#header() */ -public class ResponseHeadersAssertions { +public class HeaderAssertions { - private final ExchangeActions exchangeActions; + private final ResponseAssertions resultAssertions; private final HttpHeaders headers; - ResponseHeadersAssertions(ExchangeActions actions, HttpHeaders headers) { - this.exchangeActions = actions; + public HeaderAssertions(HttpHeaders headers, ResponseAssertions resultAssertions) { + this.resultAssertions = resultAssertions; this.headers = headers; } - public ExchangeActions cacheControl(CacheControl cacheControl) { + public ResponseAssertions valueEquals(String headerName, String... values) { + List actual = this.headers.get(headerName); + assertEquals("Response header [" + headerName + "]", Arrays.asList(values), actual); + return this.resultAssertions; + } + + public ResponseAssertions valueMatches(String headerName, String pattern) { + List values = this.headers.get(headerName); + String value = CollectionUtils.isEmpty(values) ? "" : values.get(0); + boolean match = Pattern.compile(pattern).matcher(value).matches(); + String message = "Response header " + headerName + "=\'" + value + "\' does not match " + pattern; + assertTrue(message, match); + return this.resultAssertions; + } + + public ResponseAssertions cacheControlEquals(CacheControl cacheControl) { String actual = this.headers.getCacheControl(); assertEquals("Response header Cache-Control", cacheControl.getHeaderValue(), actual); - return this.exchangeActions; + return this.resultAssertions; } - public ExchangeActions contentDisposition(ContentDisposition contentDisposition) { + public ResponseAssertions contentDispositionEquals(ContentDisposition contentDisposition) { ContentDisposition actual = this.headers.getContentDisposition(); assertEquals("Response header Content-Disposition", contentDisposition, actual); - return this.exchangeActions; + return this.resultAssertions; } - public ExchangeActions contentLength(long contentLength) { + public ResponseAssertions contentLengthEquals(long contentLength) { long actual = this.headers.getContentLength(); assertEquals("Response header Content-Length", contentLength, actual); - return this.exchangeActions; + return this.resultAssertions; } - public ExchangeActions contentType(MediaType mediaType) { + public ResponseAssertions contentTypeEquals(MediaType mediaType) { MediaType actual = this.headers.getContentType(); assertEquals("Response header Content-Type", mediaType, actual); - return this.exchangeActions; + return this.resultAssertions; } - public ExchangeActions expires(int expires) { + public ResponseAssertions expiresEquals(int expires) { long actual = this.headers.getExpires(); assertEquals("Response header Expires", expires, actual); - return this.exchangeActions; + return this.resultAssertions; } - public ExchangeActions lastModified(int lastModified) { + public ResponseAssertions lastModifiedEquals(int lastModified) { long actual = this.headers.getLastModified(); assertEquals("Response header Last-Modified", lastModified, actual); - return this.exchangeActions; - } - - /** - * Custom assertions on the response headers with a {@link Consumer}. - *

Consider using statically imported methods to improve readability - * @param consumer consumer that will apply the custom action - */ - public ExchangeActions consume(Consumer consumer) { - consumer.accept(this.headers); - return this.exchangeActions; + return this.resultAssertions; } } diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ListAssertions.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/ListAssertions.java deleted file mode 100644 index ea34dc356a..0000000000 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ListAssertions.java +++ /dev/null @@ -1,72 +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.util.Arrays; -import java.util.List; - -import static org.springframework.test.util.AssertionErrors.assertEquals; -import static org.springframework.test.util.AssertionErrors.assertTrue; - -/** - * Assertions on a List of values. - * - * @param the type of element values in the collection - * - * @author Rossen Stoyanchev - * @since 5.0 - */ -public class ListAssertions extends ObjectAssertions, ListAssertions> { - - - protected ListAssertions(ExchangeActions actions, List collection, String errorPrefix) { - super(actions, collection, errorPrefix); - } - - - /** - * Assert the size of the list. - */ - public ListAssertions hasSize(int size) { - assertEquals(getErrorPrefix() + " count", size, getValue().size()); - return this; - } - - /** - * Assert that the list contains all of the given values. - */ - @SuppressWarnings("unchecked") - public ListAssertions contains(E... entities) { - Arrays.stream(entities).forEach(entity -> { - boolean result = getValue().contains(entity); - assertTrue(getErrorPrefix() + " do not contain " + entity, result); - }); - return this; - } - - /** - * Assert the list does not contain any of the given values. - */ - @SuppressWarnings("unchecked") - public ListAssertions doesNotContain(E... entities) { - Arrays.stream(entities).forEach(entity -> { - boolean result = !getValue().contains(entity); - assertTrue(getErrorPrefix() + " should not contain " + entity, result); - }); - return this; - } - -} diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/LoggingExchangeConsumer.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/LoggingActions.java similarity index 60% rename from spring-test/src/main/java/org/springframework/test/web/reactive/server/LoggingExchangeConsumer.java rename to spring-test/src/main/java/org/springframework/test/web/reactive/server/LoggingActions.java index 00357191c0..7ce29c0b9f 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/LoggingExchangeConsumer.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/LoggingActions.java @@ -26,82 +26,85 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** - * Provides options for logging information about the performed exchange. + * Provides options for logging diagnostic information about the exchange. * * @author Rossen Stoyanchev * @since 5.0 + * @see ResponseAssertions#log() */ -public class LoggingExchangeConsumer { +public class LoggingActions { - private static Log logger = LogFactory.getLog(LoggingExchangeConsumer.class); + private static Log logger = LogFactory.getLog(LoggingActions.class); + + private final ResponseAssertions resultAssertions; + + private final ExchangeResult exchange; - private final ExchangeActions exchangeActions; - - - public LoggingExchangeConsumer(ExchangeActions exchangeActions) { - this.exchangeActions = exchangeActions; + public LoggingActions(ExchangeResult exchange, ResponseAssertions resultAssertions) { + this.resultAssertions = resultAssertions; + this.exchange = exchange; } /** * Log with {@link System#out}. */ - public ExchangeActions toConsole() { - System.out.println(getOutput()); - return this.exchangeActions; + public ResponseAssertions toConsole() { + return toOutputStream(System.out); } /** * Log with a given {@link OutputStream}. */ - public ExchangeActions toOutputStream(OutputStream stream) { + public ResponseAssertions toOutputStream(OutputStream stream) { return toWriter(new PrintWriter(stream, true)); } /** * Log with a given {@link Writer}. */ - public ExchangeActions toWriter(Writer writer) { + public ResponseAssertions toWriter(Writer writer) { try { writer.write(getOutput()); } catch (IOException ex) { throw new IllegalStateException("Failed to print exchange info", ex); } - return this.exchangeActions; + return this.resultAssertions; } /** * Log if TRACE level logging is enabled. */ - public ExchangeActions ifTraceEnabled() { + public ResponseAssertions ifTraceEnabled() { return doLog(Log::isTraceEnabled, Log::trace); } /** * Log if DEBUG level logging is enabled. */ - public ExchangeActions ifDebugEnabled() { + public ResponseAssertions ifDebugEnabled() { return doLog(Log::isDebugEnabled, Log::debug); } /** * Log if INFO level logging is enabled. */ - public ExchangeActions ifInfoEnabled() { + public ResponseAssertions ifInfoEnabled() { return doLog(Log::isInfoEnabled, Log::info); } - private ExchangeActions doLog(Predicate logLevelPredicate, BiConsumer logAction) { - if (logLevelPredicate.test(logger)) { - logAction.accept(logger, getOutput()); + + private ResponseAssertions doLog(Predicate predicate, BiConsumer consumer) { + if (predicate.test(logger)) { + consumer.accept(logger, getOutput()); } - return this.exchangeActions; + return this.resultAssertions; } private String getOutput() { - return this.exchangeActions.andReturn().toString(); + return this.resultAssertions.toString(); } } diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/MapAssertions.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/MapAssertions.java deleted file mode 100644 index 4cc5b03559..0000000000 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/MapAssertions.java +++ /dev/null @@ -1,94 +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.util.Arrays; -import java.util.Map; - -import static org.springframework.test.util.AssertionErrors.assertEquals; -import static org.springframework.test.util.AssertionErrors.assertTrue; - -/** - * Assertions on a map of values. - * - * @param the type of values in map - * - * @author Rossen Stoyanchev - * @since 5.0 - */ -public class MapAssertions extends ObjectAssertions, MapAssertions> { - - - protected MapAssertions(ExchangeActions actions, Map map, String errorPrefix) { - super(actions, map, errorPrefix); - } - - - /** - * Assert the size of the map. - */ - public MapAssertions hasSize(int size) { - assertEquals(getErrorPrefix() + " count", size, getValue().size()); - return this; - } - - /** - * Assert that the map contains all of the given values. - */ - @SuppressWarnings("unchecked") - public MapAssertions contains(K key, V value) { - V actual = getValue().get(key); - assertEquals(getErrorPrefix() + " do not contain " + value, value, actual); - return this; - } - - /** - * Assert that the map contains all of the given keys. - */ - @SuppressWarnings("unchecked") - public MapAssertions containsKeys(K... keys) { - Arrays.stream(keys).forEach(key -> { - boolean result = getValue().containsKey(key); - assertTrue(getErrorPrefix() + " does not contain key " + key, result); - }); - return this; - } - - /** - * Assert that the map contains all of the given keys. - */ - @SuppressWarnings("unchecked") - public MapAssertions containsValues(V... values) { - Arrays.stream(values).forEach(value -> { - boolean result = getValue().containsValue(value); - assertTrue(getErrorPrefix() + " does not contain value " + value, result); - }); - return this; - } - - /** - * Assert the map does not contain any of the given keys. - */ - @SuppressWarnings("unchecked") - public MapAssertions doesNotContain(K... keys) { - Arrays.stream(keys).forEach(key -> { - boolean result = !getValue().containsKey(key); - assertTrue(getErrorPrefix() + " should not contain " + key, result); - }); - return this; - } - -} diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/MultiValueMapEntryAssertions.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/MultiValueMapEntryAssertions.java deleted file mode 100644 index 0a085408fc..0000000000 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/MultiValueMapEntryAssertions.java +++ /dev/null @@ -1,107 +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.util.Arrays; -import java.util.List; - -import org.springframework.util.MultiValueMap; - -import static org.springframework.test.util.AssertionErrors.assertEquals; -import static org.springframework.test.util.AssertionErrors.assertTrue; -import static org.springframework.test.util.AssertionErrors.fail; - -/** - * Assertions on the values of a {@link MultiValueMap} entry. - * - * @param the type of values in the map. - * - * @author Rossen Stoyanchev - * @since 5.0 - */ -public class MultiValueMapEntryAssertions { - - private final ExchangeActions exchangeActions; - - private final String name; - - private final MultiValueMap map; - - private final String errorMessagePrefix; - - - MultiValueMapEntryAssertions(ExchangeActions actions, String name, - MultiValueMap map, String errorMessagePrefix) { - - this.exchangeActions = actions; - this.name = name; - this.map = map; - this.errorMessagePrefix = errorMessagePrefix + " " + this.name; - } - - - /** - * The given values are equal to the actual values. - * @param values the values to match - */ - @SuppressWarnings("unchecked") - public ExchangeActions isEqualTo(V... values) { - List actual = this.map.get(this.name); - assertEquals(this.errorMessagePrefix, Arrays.asList(values), actual); - return this.exchangeActions; - } - - /** - * The list of actual values contains the given values. - * @param values the values to match - */ - @SuppressWarnings("unchecked") - public ExchangeActions hasValues(V... values) { - List actual = this.map.get(this.name); - List expected = Arrays.asList(values); - String message = getErrorMessagePrefix() + " does not contain " + expected; - assertTrue(message, actual.containsAll(expected)); - return this.exchangeActions; - } - - - // Protected methods for sub-classes - - protected ExchangeActions getExchangeActions() { - return this.exchangeActions; - } - - protected String getName() { - return this.name; - } - - protected MultiValueMap getMap() { - return this.map; - } - - protected String getErrorMessagePrefix() { - return this.errorMessagePrefix; - } - - protected V getValue(int index) { - List actualValues = getMap().get(getName()); - if (actualValues == null || index >= actualValues.size()) { - fail(getErrorMessagePrefix() + " does not have values at index[" + index + "]: " + actualValues); - } - return actualValues.get(index); - } - -} diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ObjectAssertions.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/ObjectAssertions.java deleted file mode 100644 index 733e775c7d..0000000000 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ObjectAssertions.java +++ /dev/null @@ -1,129 +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.time.Duration; -import java.util.function.Consumer; -import java.util.function.Supplier; - -import org.springframework.web.reactive.function.client.ClientResponse; - -import static org.springframework.test.util.AssertionErrors.assertEquals; - -/** - * Assertions on an Object. - * - * @param the type of Object to apply assertions to - * @param assertion method return type - * - * @author Rossen Stoyanchev - * @since 5.0 - */ -public class ObjectAssertions> { - - private final ExchangeActions exchangeActions; - - private V value; - - private final Supplier valueSupplier; - - private final String errorPrefix; - - - protected ObjectAssertions(ExchangeActions exchangeActions, V value, String errorPrefix) { - this(exchangeActions, () -> value, errorPrefix); - } - - protected ObjectAssertions(ExchangeActions exchangeActions, Supplier valueSupplier, String errorPrefix) { - this.exchangeActions = exchangeActions; - this.valueSupplier = valueSupplier; - this.errorPrefix = errorPrefix; - } - - - /** - * Assert the actual value is equal to the given expected value. - */ - public T isEqualTo(V expected) { - assertEquals(this.errorPrefix, expected, getValue()); - return self(); - } - - /** - * Assert the actual value is not equal to the expected value. - */ - public T isNotEqualTo(V expected) { - assertEquals(this.errorPrefix, expected, getValue()); - return self(); - } - - /** - * Custom assertions on the Object value with a {@link Consumer}. - *

Consider using statically imported methods to improve readability - * @param consumer consumer that will apply the custom action - */ - public ExchangeActions consume(Consumer consumer) { - consumer.accept(this.getValue()); - return this.exchangeActions; - } - - - /** - * Continue with more assertions or actions on the response. - */ - public ExchangeActions and() { - return this.exchangeActions; - } - - /** - * Return {@link ExchangeInfo} for direct access to request and response. - */ - public ExchangeInfo andReturn() { - return this.exchangeActions.andReturn(); - } - - - // Protected methods for sub-classes - - protected ExchangeActions getExchangeActions() { - return this.exchangeActions; - } - - protected ClientResponse getResponse() { - return this.exchangeActions.andReturn().getResponse(); - } - - protected Duration getTimeout() { - return this.exchangeActions.andReturn().getResponseTimeout(); - } - - protected String getErrorPrefix() { - return this.errorPrefix; - } - - protected V getValue() { - if (this.value == null) { - this.value = this.valueSupplier.get(); - } - return this.value; - } - - @SuppressWarnings("unchecked") - protected T self() { - return (T) this; - } - -} diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ResponseAssertions.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/ResponseAssertions.java new file mode 100644 index 0000000000..19a4ccd497 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/ResponseAssertions.java @@ -0,0 +1,66 @@ +/* + * 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 static org.springframework.test.util.AssertionErrors.assertEquals; + +/** + * Assertions on an {@code ExchangeResult}. + * + *

Use {@link ExchangeResult#assertThat()} to access these assertions. + * + * @author Rossen Stoyanchev + * @since 5.0 + */ +public class ResponseAssertions { + + private final ExchangeResult exchangeResult; + + + ResponseAssertions(ExchangeResult exchangeResult) { + this.exchangeResult = exchangeResult; + } + + + /** + * Assertions on the response status. + */ + public StatusAssertions status() { + return new StatusAssertions<>(this.exchangeResult.getResponseStatus(), this); + } + + /** + * Assertions on response headers. + */ + public HeaderAssertions header() { + return new HeaderAssertions<>(this.exchangeResult.getResponseHeaders(), this); + } + + /** + * Assert the response body is equal to the given value. + */ + public void bodyEquals(T value) { + assertEquals("Response body", value, this.exchangeResult.getResponseBody()); + } + + /** + * Options for logging diagnostic information. + */ + public LoggingActions log() { + return new LoggingActions(this.exchangeResult, this); + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ResponseBodyAssertions.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/ResponseBodyAssertions.java deleted file mode 100644 index cf01c162e4..0000000000 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ResponseBodyAssertions.java +++ /dev/null @@ -1,75 +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.nio.ByteBuffer; -import java.time.Duration; -import java.util.Map; - -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import org.springframework.core.ResolvableType; -import org.springframework.web.reactive.function.client.ClientResponse; - -import static org.springframework.web.reactive.function.BodyExtractors.toMono; - -/** - * Assertions on the body of the response. - * - * @author Rossen Stoyanchev - * @since 5.0 - */ -public class ResponseBodyAssertions { - - private final ExchangeActions exchangeActions; - - - public ResponseBodyAssertions(ExchangeActions exchangeActions) { - this.exchangeActions = exchangeActions; - } - - - /** - * Assert the response does not have any content. - */ - public ExchangeActions isEmpty() { - Flux body = getResponse().bodyToFlux(ByteBuffer.class); - StepVerifier.create(body).expectComplete().verify(getTimeout()); - return this.exchangeActions; - } - - /** - * Assert the response decoded as a Map of String key-value pairs. - */ - public MapAssertions asMap() { - ResolvableType type = ResolvableType.forClassWithGenerics(Map.class, String.class, String.class); - Mono> mono = getResponse().body(toMono(type)); - Map map = mono.block(getTimeout()); - return new MapAssertions<>(this.exchangeActions, map, "Response body map"); - } - - - private ClientResponse getResponse() { - return this.exchangeActions.andReturn().getResponse(); - } - - private Duration getTimeout() { - return this.exchangeActions.andReturn().getResponseTimeout(); - } - -} diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ResponseEntityAssertions.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/ResponseEntityAssertions.java deleted file mode 100644 index dd38377f23..0000000000 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ResponseEntityAssertions.java +++ /dev/null @@ -1,83 +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.util.List; -import java.util.Map; - -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import org.springframework.core.ResolvableType; - -import static org.springframework.web.reactive.function.BodyExtractors.toFlux; -import static org.springframework.web.reactive.function.BodyExtractors.toMono; - -/** - * Assertions on the response body decoded as a single entity. - * - * @param the response entity type - * - * @author Rossen Stoyanchev - * @since 5.0 - */ -public class ResponseEntityAssertions extends ObjectAssertions> { - - private final ResolvableType entityType; - - - ResponseEntityAssertions(ExchangeActions actions, ResolvableType entityType) { - super(actions, () -> initEntity(actions, entityType), "Response body"); - this.entityType = entityType; - } - - private static T initEntity(ExchangeActions exchangeActions, ResolvableType entityType) { - ExchangeInfo info = exchangeActions.andReturn(); - Mono mono = info.getResponse().body(toMono(entityType)); - return mono.block(info.getResponseTimeout()); - } - - - /** - * Assert the response decoded as a List of entities of the given type. - */ - public ListAssertions list() { - Flux flux = getResponse().body(toFlux(this.entityType)); - List list = flux.collectList().block(getTimeout()); - return new ListAssertions(getExchangeActions(), list, "Response entity collection"); - } - - /** - * Assert the response decoded as a Map of entities with String keys. - */ - public MapAssertions map() { - ResolvableType keyType = ResolvableType.forClass(String.class); - ResolvableType type = ResolvableType.forClassWithGenerics(Map.class, keyType, this.entityType); - Mono> mono = getResponse().body(toMono(type)); - Map map = mono.block(getTimeout()); - return new MapAssertions<>(getExchangeActions(), map, "Response entity map"); - } - - /** - * Assert the response content using a {@link StepVerifier}. - */ - public StepVerifier.FirstStep stepVerifier() { - Flux flux = getResponse().body(toFlux(this.entityType)); - return StepVerifier.create(flux); - } - -} diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ResponseHeaderAssertions.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/ResponseHeaderAssertions.java deleted file mode 100644 index 8af24339ea..0000000000 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ResponseHeaderAssertions.java +++ /dev/null @@ -1,47 +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.util.List; - -import org.springframework.util.CollectionUtils; - -/** - * Assertions on the values of a specific header. - * - * @author Rossen Stoyanchev - * @since 5.0 - */ -public class ResponseHeaderAssertions extends StringAssertions { - - private final List values; - - - ResponseHeaderAssertions(ExchangeActions actions, String headerName, List values) { - super(actions, initHeaderValue(values), "Response header [" + headerName + "]"); - this.values = values; - } - - private static String initHeaderValue(List values) { - return CollectionUtils.isEmpty(values) ? null : values.get(0); - } - - - public ListAssertions values() { - return new ListAssertions<>(getExchangeActions(), this.values, getErrorPrefix()); - } - -} diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ResponseStatusAssertions.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/ResponseStatusAssertions.java deleted file mode 100644 index 34772f86e5..0000000000 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ResponseStatusAssertions.java +++ /dev/null @@ -1,606 +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.util.function.Consumer; - -import org.springframework.http.HttpStatus; - -import static org.springframework.test.util.AssertionErrors.assertEquals; - -/** - * Provides methods for asserting the response status. - * - * @author Rossen Stoyanchev - * @since 5.0 - */ -@SuppressWarnings("unused") -public class ResponseStatusAssertions { - - private final ExchangeActions exchangeActions; - - private final HttpStatus httpStatus; - - - ResponseStatusAssertions(ExchangeActions actions) { - this.exchangeActions = actions; - this.httpStatus = actions.andReturn().getResponse().statusCode(); - } - - - public ExchangeActions is(int status) { - assertEquals("Response status", status, this.httpStatus.value()); - return this.exchangeActions; - } - - /** - * Assert the response status code is in the 1xx range. - */ - public ExchangeActions is1xxInformational() { - String message = "Range for response status value " + this.httpStatus; - assertEquals(message, HttpStatus.Series.INFORMATIONAL, this.httpStatus.series()); - return this.exchangeActions; - } - - /** - * Assert the response status code is in the 2xx range. - */ - public ExchangeActions is2xxSuccessful() { - String message = "Range for response status value " + this.httpStatus; - assertEquals(message, HttpStatus.Series.SUCCESSFUL, this.httpStatus.series()); - return this.exchangeActions; - } - - /** - * Assert the response status code is in the 3xx range. - */ - public ExchangeActions is3xxRedirection() { - String message = "Range for response status value " + this.httpStatus; - assertEquals(message, HttpStatus.Series.REDIRECTION, this.httpStatus.series()); - return this.exchangeActions; - } - - /** - * Assert the response status code is in the 4xx range. - */ - public ExchangeActions is4xxClientError() { - String message = "Range for response status value " + this.httpStatus; - assertEquals(message, HttpStatus.Series.CLIENT_ERROR, this.httpStatus.series()); - return this.exchangeActions; - } - - /** - * Assert the response status code is in the 5xx range. - */ - public ExchangeActions is5xxServerError() { - String message = "Range for response status value " + this.httpStatus; - assertEquals(message, HttpStatus.Series.SERVER_ERROR, this.httpStatus.series()); - return this.exchangeActions; - } - - /** - * Custom assertions on the response status with a {@link Consumer}. - *

Consider using statically imported methods to improve readability - * @param consumer consumer that will apply the custom action - */ - public ExchangeActions consume(Consumer consumer) { - consumer.accept(this.httpStatus); - return this.exchangeActions; - } - - /** - * Assert the response error message. - */ - public ExchangeActions reason(String reason) { - assertEquals("Response status reason", reason, this.httpStatus.getReasonPhrase()); - return this.exchangeActions; - } - - /** - * Assert the response status code is {@code HttpStatus.CONTINUE} (100). - */ - public ExchangeActions isContinue() { - return assertStatusIsEqualTo(HttpStatus.CONTINUE); - } - - /** - * Assert the response status code is {@code HttpStatus.SWITCHING_PROTOCOLS} (101). - */ - public ExchangeActions isSwitchingProtocols() { - return assertStatusIsEqualTo(HttpStatus.SWITCHING_PROTOCOLS); - } - - /** - * Assert the response status code is {@code HttpStatus.PROCESSING} (102). - */ - public ExchangeActions isProcessing() { - return assertStatusIsEqualTo(HttpStatus.PROCESSING); - } - - /** - * Assert the response status code is {@code HttpStatus.CHECKPOINT} (103). - */ - public ExchangeActions isCheckpoint() { - return assertStatusIsEqualTo(HttpStatus.valueOf(103)); - } - - /** - * Assert the response status code is {@code HttpStatus.OK} (200). - */ - public ExchangeActions isOk() { - return assertStatusIsEqualTo(HttpStatus.OK); - } - - /** - * Assert the response status code is {@code HttpStatus.CREATED} (201). - */ - public ExchangeActions isCreated() { - return assertStatusIsEqualTo(HttpStatus.CREATED); - } - - /** - * Assert the response status code is {@code HttpStatus.ACCEPTED} (202). - */ - public ExchangeActions isAccepted() { - return assertStatusIsEqualTo(HttpStatus.ACCEPTED); - } - - /** - * Assert the response status code is {@code HttpStatus.NON_AUTHORITATIVE_INFORMATION} (203). - */ - public ExchangeActions isNonAuthoritativeInformation() { - return assertStatusIsEqualTo(HttpStatus.NON_AUTHORITATIVE_INFORMATION); - } - - /** - * Assert the response status code is {@code HttpStatus.NO_CONTENT} (204). - */ - public ExchangeActions isNoContent() { - return assertStatusIsEqualTo(HttpStatus.NO_CONTENT); - } - - /** - * Assert the response status code is {@code HttpStatus.RESET_CONTENT} (205). - */ - public ExchangeActions isResetContent() { - return assertStatusIsEqualTo(HttpStatus.RESET_CONTENT); - } - - /** - * Assert the response status code is {@code HttpStatus.PARTIAL_CONTENT} (206). - */ - public ExchangeActions isPartialContent() { - return assertStatusIsEqualTo(HttpStatus.PARTIAL_CONTENT); - } - - /** - * Assert the response status code is {@code HttpStatus.MULTI_STATUS} (207). - */ - public ExchangeActions isMultiStatus() { - return assertStatusIsEqualTo(HttpStatus.MULTI_STATUS); - } - - /** - * Assert the response status code is {@code HttpStatus.ALREADY_REPORTED} (208). - */ - public ExchangeActions isAlreadyReported() { - return assertStatusIsEqualTo(HttpStatus.ALREADY_REPORTED); - } - - /** - * Assert the response status code is {@code HttpStatus.IM_USED} (226). - */ - public ExchangeActions isImUsed() { - return assertStatusIsEqualTo(HttpStatus.IM_USED); - } - - /** - * Assert the response status code is {@code HttpStatus.MULTIPLE_CHOICES} (300). - */ - public ExchangeActions isMultipleChoices() { - return assertStatusIsEqualTo(HttpStatus.MULTIPLE_CHOICES); - } - - /** - * Assert the response status code is {@code HttpStatus.MOVED_PERMANENTLY} (301). - */ - public ExchangeActions isMovedPermanently() { - return assertStatusIsEqualTo(HttpStatus.MOVED_PERMANENTLY); - } - - /** - * Assert the response status code is {@code HttpStatus.FOUND} (302). - */ - public ExchangeActions isFound() { - return assertStatusIsEqualTo(HttpStatus.FOUND); - } - - /** - * Assert the response status code is {@code HttpStatus.MOVED_TEMPORARILY} (302). - * @see #isFound() - * @deprecated in favor of {@link #isFound()} - */ - @Deprecated - public ExchangeActions isMovedTemporarily() { - return assertStatusIsEqualTo(HttpStatus.MOVED_TEMPORARILY); - } - - /** - * Assert the response status code is {@code HttpStatus.SEE_OTHER} (303). - */ - public ExchangeActions isSeeOther() { - return assertStatusIsEqualTo(HttpStatus.SEE_OTHER); - } - - /** - * Assert the response status code is {@code HttpStatus.NOT_MODIFIED} (304). - */ - public ExchangeActions isNotModified() { - return assertStatusIsEqualTo(HttpStatus.NOT_MODIFIED); - } - - /** - * Assert the response status code is {@code HttpStatus.USE_PROXY} (305). - * @deprecated matching the deprecation of {@code HttpStatus.USE_PROXY} - */ - @Deprecated - public ExchangeActions isUseProxy() { - return assertStatusIsEqualTo(HttpStatus.USE_PROXY); - } - - /** - * Assert the response status code is {@code HttpStatus.TEMPORARY_REDIRECT} (307). - */ - public ExchangeActions isTemporaryRedirect() { - return assertStatusIsEqualTo(HttpStatus.TEMPORARY_REDIRECT); - } - - /** - * Assert the response status code is {@code HttpStatus.PERMANENT_REDIRECT} (308). - */ - public ExchangeActions isPermanentRedirect() { - return assertStatusIsEqualTo(HttpStatus.valueOf(308)); - } - - /** - * Assert the response status code is {@code HttpStatus.BAD_REQUEST} (400). - */ - public ExchangeActions isBadRequest() { - return assertStatusIsEqualTo(HttpStatus.BAD_REQUEST); - } - - /** - * Assert the response status code is {@code HttpStatus.UNAUTHORIZED} (401). - */ - public ExchangeActions isUnauthorized() { - return assertStatusIsEqualTo(HttpStatus.UNAUTHORIZED); - } - - /** - * Assert the response status code is {@code HttpStatus.PAYMENT_REQUIRED} (402). - */ - public ExchangeActions isPaymentRequired() { - return assertStatusIsEqualTo(HttpStatus.PAYMENT_REQUIRED); - } - - /** - * Assert the response status code is {@code HttpStatus.FORBIDDEN} (403). - */ - public ExchangeActions isForbidden() { - return assertStatusIsEqualTo(HttpStatus.FORBIDDEN); - } - - /** - * Assert the response status code is {@code HttpStatus.NOT_FOUND} (404). - */ - public ExchangeActions isNotFound() { - return assertStatusIsEqualTo(HttpStatus.NOT_FOUND); - } - - /** - * Assert the response status code is {@code HttpStatus.METHOD_NOT_ALLOWED} (405). - */ - public ExchangeActions isMethodNotAllowed() { - return assertStatusIsEqualTo(HttpStatus.METHOD_NOT_ALLOWED); - } - - /** - * Assert the response status code is {@code HttpStatus.NOT_ACCEPTABLE} (406). - */ - public ExchangeActions isNotAcceptable() { - return assertStatusIsEqualTo(HttpStatus.NOT_ACCEPTABLE); - } - - /** - * Assert the response status code is {@code HttpStatus.PROXY_AUTHENTICATION_REQUIRED} (407). - */ - public ExchangeActions isProxyAuthenticationRequired() { - return assertStatusIsEqualTo(HttpStatus.PROXY_AUTHENTICATION_REQUIRED); - } - - /** - * Assert the response status code is {@code HttpStatus.REQUEST_TIMEOUT} (408). - */ - public ExchangeActions isRequestTimeout() { - return assertStatusIsEqualTo(HttpStatus.REQUEST_TIMEOUT); - } - - /** - * Assert the response status code is {@code HttpStatus.CONFLICT} (409). - */ - public ExchangeActions isConflict() { - return assertStatusIsEqualTo(HttpStatus.CONFLICT); - } - - /** - * Assert the response status code is {@code HttpStatus.GONE} (410). - */ - public ExchangeActions isGone() { - return assertStatusIsEqualTo(HttpStatus.GONE); - } - - /** - * Assert the response status code is {@code HttpStatus.LENGTH_REQUIRED} (411). - */ - public ExchangeActions isLengthRequired() { - return assertStatusIsEqualTo(HttpStatus.LENGTH_REQUIRED); - } - - /** - * Assert the response status code is {@code HttpStatus.PRECONDITION_FAILED} (412). - */ - public ExchangeActions isPreconditionFailed() { - return assertStatusIsEqualTo(HttpStatus.PRECONDITION_FAILED); - } - - /** - * Assert the response status code is {@code HttpStatus.PAYLOAD_TOO_LARGE} (413). - * @since 4.1 - */ - public ExchangeActions isPayloadTooLarge() { - return assertStatusIsEqualTo(HttpStatus.PAYLOAD_TOO_LARGE); - } - - /** - * Assert the response status code is {@code HttpStatus.REQUEST_ENTITY_TOO_LARGE} (413). - * @deprecated matching the deprecation of {@code HttpStatus.REQUEST_ENTITY_TOO_LARGE} - * @see #isPayloadTooLarge() - */ - @Deprecated - public ExchangeActions isRequestEntityTooLarge() { - return assertStatusIsEqualTo(HttpStatus.REQUEST_ENTITY_TOO_LARGE); - } - - /** - * Assert the response status code is {@code HttpStatus.REQUEST_URI_TOO_LONG} (414). - * @since 4.1 - */ - public ExchangeActions isUriTooLong() { - return assertStatusIsEqualTo(HttpStatus.URI_TOO_LONG); - } - - /** - * Assert the response status code is {@code HttpStatus.REQUEST_URI_TOO_LONG} (414). - * @deprecated matching the deprecation of {@code HttpStatus.REQUEST_URI_TOO_LONG} - * @see #isUriTooLong() - */ - @Deprecated - public ExchangeActions isRequestUriTooLong() { - return assertStatusIsEqualTo(HttpStatus.REQUEST_URI_TOO_LONG); - } - - /** - * Assert the response status code is {@code HttpStatus.UNSUPPORTED_MEDIA_TYPE} (415). - */ - public ExchangeActions isUnsupportedMediaType() { - return assertStatusIsEqualTo(HttpStatus.UNSUPPORTED_MEDIA_TYPE); - } - - /** - * Assert the response status code is {@code HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE} (416). - */ - public ExchangeActions isRequestedRangeNotSatisfiable() { - return assertStatusIsEqualTo(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE); - } - - /** - * Assert the response status code is {@code HttpStatus.EXPECTATION_FAILED} (417). - */ - public ExchangeActions isExpectationFailed() { - return assertStatusIsEqualTo(HttpStatus.EXPECTATION_FAILED); - } - - /** - * Assert the response status code is {@code HttpStatus.I_AM_A_TEAPOT} (418). - */ - public ExchangeActions isIAmATeapot() { - return assertStatusIsEqualTo(HttpStatus.valueOf(418)); - } - - /** - * Assert the response status code is {@code HttpStatus.INSUFFICIENT_SPACE_ON_RESOURCE} (419). - * @deprecated matching the deprecation of {@code HttpStatus.INSUFFICIENT_SPACE_ON_RESOURCE} - */ - @Deprecated - public ExchangeActions isInsufficientSpaceOnResource() { - return assertStatusIsEqualTo(HttpStatus.INSUFFICIENT_SPACE_ON_RESOURCE); - } - - /** - * Assert the response status code is {@code HttpStatus.METHOD_FAILURE} (420). - * @deprecated matching the deprecation of {@code HttpStatus.METHOD_FAILURE} - */ - @Deprecated - public ExchangeActions isMethodFailure() { - return assertStatusIsEqualTo(HttpStatus.METHOD_FAILURE); - } - - /** - * Assert the response status code is {@code HttpStatus.DESTINATION_LOCKED} (421). - * @deprecated matching the deprecation of {@code HttpStatus.DESTINATION_LOCKED} - */ - @Deprecated - public ExchangeActions isDestinationLocked() { - return assertStatusIsEqualTo(HttpStatus.DESTINATION_LOCKED); - } - - /** - * Assert the response status code is {@code HttpStatus.UNPROCESSABLE_ENTITY} (422). - */ - public ExchangeActions isUnprocessableEntity() { - return assertStatusIsEqualTo(HttpStatus.UNPROCESSABLE_ENTITY); - } - - /** - * Assert the response status code is {@code HttpStatus.LOCKED} (423). - */ - public ExchangeActions isLocked() { - return assertStatusIsEqualTo(HttpStatus.LOCKED); - } - - /** - * Assert the response status code is {@code HttpStatus.FAILED_DEPENDENCY} (424). - */ - public ExchangeActions isFailedDependency() { - return assertStatusIsEqualTo(HttpStatus.FAILED_DEPENDENCY); - } - - /** - * Assert the response status code is {@code HttpStatus.UPGRADE_REQUIRED} (426). - */ - public ExchangeActions isUpgradeRequired() { - return assertStatusIsEqualTo(HttpStatus.UPGRADE_REQUIRED); - } - - /** - * Assert the response status code is {@code HttpStatus.PRECONDITION_REQUIRED} (428). - */ - public ExchangeActions isPreconditionRequired() { - return assertStatusIsEqualTo(HttpStatus.valueOf(428)); - } - - /** - * Assert the response status code is {@code HttpStatus.TOO_MANY_REQUESTS} (429). - */ - public ExchangeActions isTooManyRequests() { - return assertStatusIsEqualTo(HttpStatus.valueOf(429)); - } - - /** - * Assert the response status code is {@code HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE} (431). - */ - public ExchangeActions isRequestHeaderFieldsTooLarge() { - return assertStatusIsEqualTo(HttpStatus.valueOf(431)); - } - - /** - * Assert the response status code is {@code HttpStatus.UNAVAILABLE_FOR_LEGAL_REASONS} (451). - * @since 4.3 - */ - public ExchangeActions isUnavailableForLegalReasons() { - return assertStatusIsEqualTo(HttpStatus.valueOf(451)); - } - - /** - * Assert the response status code is {@code HttpStatus.INTERNAL_SERVER_ERROR} (500). - */ - public ExchangeActions isInternalServerError() { - return assertStatusIsEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); - } - - /** - * Assert the response status code is {@code HttpStatus.NOT_IMPLEMENTED} (501). - */ - public ExchangeActions isNotImplemented() { - return assertStatusIsEqualTo(HttpStatus.NOT_IMPLEMENTED); - } - - /** - * Assert the response status code is {@code HttpStatus.BAD_GATEWAY} (502). - */ - public ExchangeActions isBadGateway() { - return assertStatusIsEqualTo(HttpStatus.BAD_GATEWAY); - } - - /** - * Assert the response status code is {@code HttpStatus.SERVICE_UNAVAILABLE} (503). - */ - public ExchangeActions isServiceUnavailable() { - return assertStatusIsEqualTo(HttpStatus.SERVICE_UNAVAILABLE); - } - - /** - * Assert the response status code is {@code HttpStatus.GATEWAY_TIMEOUT} (504). - */ - public ExchangeActions isGatewayTimeout() { - return assertStatusIsEqualTo(HttpStatus.GATEWAY_TIMEOUT); - } - - /** - * Assert the response status code is {@code HttpStatus.HTTP_VERSION_NOT_SUPPORTED} (505). - */ - public ExchangeActions isHttpVersionNotSupported() { - return assertStatusIsEqualTo(HttpStatus.HTTP_VERSION_NOT_SUPPORTED); - } - - /** - * Assert the response status code is {@code HttpStatus.VARIANT_ALSO_NEGOTIATES} (506). - */ - public ExchangeActions isVariantAlsoNegotiates() { - return assertStatusIsEqualTo(HttpStatus.VARIANT_ALSO_NEGOTIATES); - } - - /** - * Assert the response status code is {@code HttpStatus.INSUFFICIENT_STORAGE} (507). - */ - public ExchangeActions isInsufficientStorage() { - return assertStatusIsEqualTo(HttpStatus.INSUFFICIENT_STORAGE); - } - - /** - * Assert the response status code is {@code HttpStatus.LOOP_DETECTED} (508). - */ - public ExchangeActions isLoopDetected() { - return assertStatusIsEqualTo(HttpStatus.LOOP_DETECTED); - } - - /** - * Assert the response status code is {@code HttpStatus.BANDWIDTH_LIMIT_EXCEEDED} (509). - */ - public ExchangeActions isBandwidthLimitExceeded() { - return assertStatusIsEqualTo(HttpStatus.valueOf(509)); - } - - /** - * Assert the response status code is {@code HttpStatus.NOT_EXTENDED} (510). - */ - public ExchangeActions isNotExtended() { - return assertStatusIsEqualTo(HttpStatus.NOT_EXTENDED); - } - - /** - * Assert the response status code is {@code HttpStatus.NETWORK_AUTHENTICATION_REQUIRED} (511). - */ - public ExchangeActions isNetworkAuthenticationRequired() { - return assertStatusIsEqualTo(HttpStatus.valueOf(511)); - } - - private ExchangeActions assertStatusIsEqualTo(final HttpStatus status) { - assertEquals("Status", status, this.httpStatus); - return this.exchangeActions; - } - -} diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/StatusAssertions.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/StatusAssertions.java new file mode 100644 index 0000000000..8f7d04cbf5 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/StatusAssertions.java @@ -0,0 +1,200 @@ +/* + * 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 org.springframework.http.HttpStatus; + +import static org.springframework.test.util.AssertionErrors.assertEquals; + +/** + * Assertions on the status of a response. + * + * @author Rossen Stoyanchev + * @since 5.0 + * @see ResponseAssertions#status() + */ +@SuppressWarnings("unused") +public class StatusAssertions { + + private final ResponseAssertions resultAssertions; + + private final HttpStatus httpStatus; + + + StatusAssertions(HttpStatus status, ResponseAssertions exchangeActions) { + this.resultAssertions = exchangeActions; + this.httpStatus = status; + } + + + /** + * Assert the response status as an {@link HttpStatus}. + */ + public ResponseAssertions isEqualTo(HttpStatus status) { + assertEquals("Response status", status, this.httpStatus); + return this.resultAssertions; + } + + /** + * Assert the response status as an integer. + */ + public ResponseAssertions isEqualTo(int status) { + assertEquals("Response status", status, this.httpStatus.value()); + return this.resultAssertions; + } + + /** + * Assert the response status code is {@code HttpStatus.OK} (200). + */ + public ResponseAssertions isOk() { + assertEquals("Status", HttpStatus.OK, this.httpStatus); + return this.resultAssertions; + } + + /** + * Assert the response status code is {@code HttpStatus.CREATED} (201). + */ + public ResponseAssertions isCreated() { + assertEquals("Status", HttpStatus.CREATED, this.httpStatus); + return this.resultAssertions; + } + + /** + * Assert the response status code is {@code HttpStatus.ACCEPTED} (202). + */ + public ResponseAssertions isAccepted() { + assertEquals("Status", HttpStatus.ACCEPTED, this.httpStatus); + return this.resultAssertions; + } + + /** + * Assert the response status code is {@code HttpStatus.NO_CONTENT} (204). + */ + public ResponseAssertions isNoContent() { + assertEquals("Status", HttpStatus.NO_CONTENT, this.httpStatus); + return this.resultAssertions; + } + + /** + * Assert the response status code is {@code HttpStatus.FOUND} (302). + */ + public ResponseAssertions isFound() { + assertEquals("Status", HttpStatus.FOUND, this.httpStatus); + return this.resultAssertions; + } + + /** + * Assert the response status code is {@code HttpStatus.SEE_OTHER} (303). + */ + public ResponseAssertions isSeeOther() { + assertEquals("Status", HttpStatus.SEE_OTHER, this.httpStatus); + return this.resultAssertions; + } + + /** + * Assert the response status code is {@code HttpStatus.NOT_MODIFIED} (304). + */ + public ResponseAssertions isNotModified() { + assertEquals("Status", HttpStatus.NOT_MODIFIED, this.httpStatus); + return this.resultAssertions; + } + + /** + * Assert the response status code is {@code HttpStatus.TEMPORARY_REDIRECT} (307). + */ + public ResponseAssertions isTemporaryRedirect() { + assertEquals("Status", HttpStatus.TEMPORARY_REDIRECT, this.httpStatus); + return this.resultAssertions; + } + + /** + * Assert the response status code is {@code HttpStatus.PERMANENT_REDIRECT} (308). + */ + public ResponseAssertions isPermanentRedirect() { + assertEquals("Status", HttpStatus.PERMANENT_REDIRECT, this.httpStatus); + return this.resultAssertions; + } + + /** + * Assert the response status code is {@code HttpStatus.BAD_REQUEST} (400). + */ + public ResponseAssertions isBadRequest() { + assertEquals("Status", HttpStatus.BAD_REQUEST, this.httpStatus); + return this.resultAssertions; + } + + /** + * Assert the response status code is {@code HttpStatus.NOT_FOUND} (404). + */ + public ResponseAssertions isNotFound() { + assertEquals("Status", HttpStatus.NOT_FOUND, this.httpStatus); + return this.resultAssertions; + } + + /** + * Assert the response error message. + */ + public ResponseAssertions reasonEquals(String reason) { + assertEquals("Response status reason", reason, this.httpStatus.getReasonPhrase()); + return this.resultAssertions; + } + + /** + * Assert the response status code is in the 1xx range. + */ + public ResponseAssertions is1xxInformational() { + String message = "Range for response status value " + this.httpStatus; + assertEquals(message, HttpStatus.Series.INFORMATIONAL, this.httpStatus.series()); + return this.resultAssertions; + } + + /** + * Assert the response status code is in the 2xx range. + */ + public ResponseAssertions is2xxSuccessful() { + String message = "Range for response status value " + this.httpStatus; + assertEquals(message, HttpStatus.Series.SUCCESSFUL, this.httpStatus.series()); + return this.resultAssertions; + } + + /** + * Assert the response status code is in the 3xx range. + */ + public ResponseAssertions is3xxRedirection() { + String message = "Range for response status value " + this.httpStatus; + assertEquals(message, HttpStatus.Series.REDIRECTION, this.httpStatus.series()); + return this.resultAssertions; + } + + /** + * Assert the response status code is in the 4xx range. + */ + public ResponseAssertions is4xxClientError() { + String message = "Range for response status value " + this.httpStatus; + assertEquals(message, HttpStatus.Series.CLIENT_ERROR, this.httpStatus.series()); + return this.resultAssertions; + } + + /** + * Assert the response status code is in the 5xx range. + */ + public ResponseAssertions is5xxServerError() { + String message = "Range for response status value " + this.httpStatus; + assertEquals(message, HttpStatus.Series.SERVER_ERROR, this.httpStatus.series()); + return this.resultAssertions; + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/StringAssertions.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/StringAssertions.java deleted file mode 100644 index cce097b458..0000000000 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/StringAssertions.java +++ /dev/null @@ -1,72 +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.util.Arrays; -import java.util.regex.Pattern; - -import static org.springframework.test.util.AssertionErrors.assertTrue; - -/** - * Assertions on a String value. - * - * @author Rossen Stoyanchev - * @since 5.0 - */ -public class StringAssertions extends ObjectAssertions { - - - public StringAssertions(ExchangeActions exchangeActions, String value, String errorPrefix) { - super(exchangeActions, value, errorPrefix); - } - - - /** - * The value {@link String#contains contains} the given sub-strings. - * @param values the values to match - */ - public StringAssertions contains(CharSequence... values) { - Arrays.stream(values).forEach(value -> { - String message = getErrorPrefix() + " does not contain " + value; - assertTrue(message, getValue().contains(value)); - }); - return this; - } - - /** - * The value does not {@link String#contains contain} the given sub-strings. - * @param values the values to match - */ - public StringAssertions doesNotContain(CharSequence... values) { - Arrays.stream(values).forEach(value -> { - String message = getErrorPrefix() + " contains " + value + " but shouldn't"; - assertTrue(message, !getValue().contains(value)); - }); - return this; - } - - /** - * The value matches the given regex pattern value. - * @param pattern the values to be compiled with {@link Pattern} - */ - public StringAssertions matches(String pattern) { - boolean match = Pattern.compile(pattern).matcher(getValue()).matches(); - String message = getErrorPrefix() + " with value " + getValue() + " does not match " + pattern; - assertTrue(message, match); - return this; - } - -} diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java index fff068c3a1..123e1e018d 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java @@ -25,8 +25,10 @@ import java.util.function.Consumer; import java.util.function.Function; import org.reactivestreams.Publisher; +import reactor.core.publisher.Flux; import org.springframework.context.ApplicationContext; +import org.springframework.core.ResolvableType; import org.springframework.format.FormatterRegistry; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -330,12 +332,13 @@ public interface WebTestClient { /** - * Contract for specifying the URI for a request. + * Specification for providing the URI of a request. */ interface UriSpec { /** * Specify the URI using an absolute, fully constructed {@link URI}. + * @return spec to add headers or perform the exchange */ HeaderSpec uri(URI uri); @@ -343,6 +346,7 @@ public interface WebTestClient { * Specify the URI for the request using a URI template and URI variables. * If a {@link UriBuilderFactory} was configured for the client (e.g. * with a base URI) it will be used to expand the URI template. + * @return spec to add headers or perform the exchange */ HeaderSpec uri(String uri, Object... uriVariables); @@ -350,19 +354,21 @@ public interface WebTestClient { * Specify the URI for the request using a URI template and URI variables. * If a {@link UriBuilderFactory} was configured for the client (e.g. * with a base URI) it will be used to expand the URI template. + * @return spec to add headers or perform the exchange */ HeaderSpec uri(String uri, Map uriVariables); /** * Build the URI for the request with a {@link UriBuilder} obtained * through the {@link UriBuilderFactory} configured for this client. + * @return spec to add headers or perform the exchange */ HeaderSpec uri(Function uriFunction); } /** - * Contract for specifying request headers leading up to the exchange. + * Specification for adding request headers and performing an exchange. */ interface HeaderSpec { @@ -370,7 +376,7 @@ public interface WebTestClient { * Set the list of acceptable {@linkplain MediaType media types}, as * specified by the {@code Accept} header. * @param acceptableMediaTypes the acceptable media types - * @return this builder + * @return the same instance */ HeaderSpec accept(MediaType... acceptableMediaTypes); @@ -378,7 +384,7 @@ public interface WebTestClient { * Set the list of acceptable {@linkplain Charset charsets}, as specified * by the {@code Accept-Charset} header. * @param acceptableCharsets the acceptable charsets - * @return this builder + * @return the same instance */ HeaderSpec acceptCharset(Charset... acceptableCharsets); @@ -386,7 +392,7 @@ public interface WebTestClient { * Set the length of the body in bytes, as specified by the * {@code Content-Length} header. * @param contentLength the content length - * @return this builder + * @return the same instance * @see HttpHeaders#setContentLength(long) */ HeaderSpec contentLength(long contentLength); @@ -395,7 +401,7 @@ public interface WebTestClient { * Set the {@linkplain MediaType media type} of the body, as specified * by the {@code Content-Type} header. * @param contentType the content type - * @return this builder + * @return the same instance * @see HttpHeaders#setContentType(MediaType) */ HeaderSpec contentType(MediaType contentType); @@ -404,7 +410,7 @@ public interface WebTestClient { * Add a cookie with the given name and value. * @param name the cookie name * @param value the cookie value - * @return this builder + * @return the same instance */ HeaderSpec cookie(String name, String value); @@ -412,7 +418,7 @@ public interface WebTestClient { * Copy the given cookies into the entity's cookies map. * * @param cookies the existing cookies to copy from - * @return this builder + * @return the same instance */ HeaderSpec cookies(MultiValueMap cookies); @@ -421,14 +427,14 @@ public interface WebTestClient { *

The date should be specified as the number of milliseconds since * January 1, 1970 GMT. * @param ifModifiedSince the new value of the header - * @return this builder + * @return the same instance */ HeaderSpec ifModifiedSince(ZonedDateTime ifModifiedSince); /** * Set the values of the {@code If-None-Match} header. * @param ifNoneMatches the new value of the header - * @return this builder + * @return the same instance */ HeaderSpec ifNoneMatch(String... ifNoneMatches); @@ -436,42 +442,112 @@ public interface WebTestClient { * Add the given, single header value under the given name. * @param headerName the header name * @param headerValues the header value(s) - * @return this builder + * @return the same instance */ HeaderSpec header(String headerName, String... headerValues); /** * Copy the given headers into the entity's headers map. * @param headers the existing headers to copy from - * @return this builder + * @return the same instance */ HeaderSpec headers(HttpHeaders headers); /** - * Perform the request without a request body. - * @return options for asserting the response with + * Perform the exchange without a request body. + * @return spec for decoding the response */ - ExchangeActions exchange(); + ResponseSpec exchange(); /** - * Set the body of the request to the given {@code BodyInserter} and - * perform the request. - * @param inserter the {@code BodyInserter} that writes to the request - * @param the type contained in the body - * @return options for asserting the response with + * Perform the exchange with the body for the request populated using + * a {@link BodyInserter}. + * @param inserter the inserter + * @param the body type, or the the element type (for a stream) + * @return spec for decoding the response + * @see org.springframework.web.reactive.function.BodyInserters */ - ExchangeActions exchange(BodyInserter inserter); + ResponseSpec exchange(BodyInserter inserter); /** - * Set the body of the request to the given {@code Publisher} and - * perform the request. - * @param publisher the {@code Publisher} to write to the request + * Perform the exchange and use the given {@code Publisher} for the + * request body. + * @param publisher the request body data * @param elementClass the class of elements contained in the publisher * @param the type of the elements contained in the publisher * @param the type of the {@code Publisher} - * @return options for asserting the response with + * @return spec for decoding the response */ - > ExchangeActions exchange(S publisher, Class elementClass); + > ResponseSpec exchange(S publisher, Class elementClass); + } + + /** + * Specification for decoding the response of an exchange. + */ + interface ResponseSpec { + + /** + * Decode the response as a single entity of the given type. + * @param entityClass the entity type + * @param the type of entity + * @return container for the result of the exchange + */ + ExchangeResult decodeEntity(Class entityClass); + + /** + * Alternative to {@link #decodeEntity(Class)} useful for entity + * types with generics. + * @param entityType the type of entity + * @param the type of entity + * @return container for the result of the exchange + * @see ResolvableType#forClassWithGenerics(Class, Class[]) + */ + ExchangeResult decodeEntity(ResolvableType entityType); + + /** + * Decode the response as a finite stream of the given element type and + * collect the items into a list. + * @param elementClass the list element type + * @param the type of list elements + * @return container for the result of the exchange + */ + ExchangeResult> decodeAndCollect(Class elementClass); + + /** + * Alternative to {@link #decodeAndCollect(Class)} useful for element + * types with generics. + * @param elementType the list element type + * @param the type of list elements + * @return container for the result of the exchange + * @see ResolvableType#forClassWithGenerics(Class, Class[]) + */ + ExchangeResult> decodeAndCollect(ResolvableType elementType); + + /** + * Turn the response stream of byte buffers into a stream of Objects of + * the given type. + * @param elementClass the stream element type + * @param the type of stream elements + * @return container for the result of the exchange + */ + ExchangeResult> decodeFlux(Class elementClass); + + /** + * Alternative to {@link #decodeFlux(Class)} useful for element types + * with generics. + * @param elementType the stream element type + * @param the type of stream elements + * @return container for the result of the exchange + * @see ResolvableType#forClassWithGenerics(Class, Class[]) + */ + ExchangeResult> decodeFlux(ResolvableType elementType); + + /** + * Consume the response and verify there is no content. + * @return container for the result of the exchange + */ + ExchangeResult expectNoBody(); + } } diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ErrorTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ErrorTests.java index 5a2fdf6457..987fcf0edd 100644 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ErrorTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ErrorTests.java @@ -18,6 +18,7 @@ package org.springframework.test.web.reactive.server.samples; import org.junit.Before; import org.junit.Test; +import org.springframework.http.HttpStatus; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @@ -43,14 +44,18 @@ public class ErrorTests { public void notFound() throws Exception { this.client.get().uri("/invalid") .exchange() - .assertStatus().isNotFound(); + .expectNoBody() + .assertThat() + .status().isNotFound(); } @Test public void serverException() throws Exception { this.client.get().uri("/server-error") .exchange() - .assertStatus().isInternalServerError(); + .expectNoBody() + .assertThat() + .status().isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); } diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/HeaderTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/HeaderTests.java index 9cbe8b8f15..8ed6824b4a 100644 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/HeaderTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/HeaderTests.java @@ -15,8 +15,6 @@ */ package org.springframework.test.web.reactive.server.samples; -import java.util.Arrays; - import org.junit.Before; import org.junit.Test; @@ -27,8 +25,6 @@ import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import static org.junit.Assert.assertEquals; - /** * Tests with custom headers. * @@ -53,17 +49,20 @@ public class HeaderTests { public void requestResponseHeaderPair() throws Exception { this.client.get().uri("/request-response-pair").header("h1", "in") .exchange() - .assertStatus().isOk() - .assertHeader("h1").isEqualTo("in-out"); + .expectNoBody() + .assertThat() + .status().isOk() + .header().valueEquals("h1", "in-out"); } @Test - public void headerConsumer() throws Exception { + public void headerMultivalue() throws Exception { this.client.get().uri("/multivalue") .exchange() - .assertStatus().isOk() - .assertHeader("h1").consume(value -> assertEquals("v1", value)) - .assertHeader("h1").values().consume(values -> assertEquals(Arrays.asList("v1", "v2", "v3"), values)); + .expectNoBody() + .assertThat() + .status().isOk() + .header().valueEquals("h1", "v1", "v2", "v3"); } diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ResponseEntityTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ResponseEntityTests.java index 101cd12d00..c3f0e598c8 100644 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ResponseEntityTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ResponseEntityTests.java @@ -16,6 +16,7 @@ package org.springframework.test.web.reactive.server.samples; import java.net.URI; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; @@ -25,9 +26,12 @@ 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.core.ResolvableType; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.test.web.reactive.server.ExchangeResult; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -37,7 +41,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import static org.hamcrest.CoreMatchers.endsWith; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.springframework.http.MediaType.TEXT_EVENT_STREAM; @@ -62,37 +65,54 @@ public class ResponseEntityTests { public void entity() throws Exception { this.client.get().uri("/persons/John") .exchange() - .assertStatus().isOk() - .assertHeaders().contentType(MediaType.APPLICATION_JSON_UTF8) - .assertEntity(Person.class).isEqualTo(new Person("John")); + .decodeEntity(Person.class) + .assertThat() + .status().isOk() + .header().contentTypeEquals(MediaType.APPLICATION_JSON_UTF8) + .bodyEquals(new Person("John")); } @Test public void entityList() throws Exception { this.client.get().uri("/persons") .exchange() - .assertStatus().isOk() - .assertHeaders().contentType(MediaType.APPLICATION_JSON_UTF8) - .assertEntity(Person.class).list() - .hasSize(3) - .contains(new Person("Jane"), new Person("Jason"), new Person("John")); + .decodeAndCollect(Person.class) + .assertThat() + .status().isOk() + .header().contentTypeEquals(MediaType.APPLICATION_JSON_UTF8) + .bodyEquals(Arrays.asList(new Person("Jane"), new Person("Jason"), new Person("John"))); } @Test public void entityMap() throws Exception { + + Map map = new LinkedHashMap<>(); + map.put("Jane", new Person("Jane")); + map.put("Jason", new Person("Jason")); + map.put("John", new Person("John")); + this.client.get().uri("/persons?map=true") .exchange() - .assertStatus().isOk() - .assertEntity(Person.class).map().hasSize(3).containsKeys("Jane", "Jason", "John"); + .decodeEntity(ResolvableType.forClassWithGenerics(Map.class, String.class, Person.class)) + .assertThat() + .status().isOk() + .bodyEquals(map); } @Test public void entityStream() throws Exception { - this.client.get().uri("/persons").accept(TEXT_EVENT_STREAM) + + ExchangeResult> result = this.client.get() + .uri("/persons") + .accept(TEXT_EVENT_STREAM) .exchange() - .assertStatus().isOk() - .assertHeaders().contentType(TEXT_EVENT_STREAM) - .assertEntity(Person.class).stepVerifier() + .decodeFlux(Person.class); + + result.assertThat() + .status().isOk() + .header().contentTypeEquals(TEXT_EVENT_STREAM); + + StepVerifier.create(result.getResponseBody()) .expectNext(new Person("N0"), new Person("N1"), new Person("N2")) .expectNextCount(4) .consumeNextWith(person -> assertThat(person.getName(), endsWith("7"))) @@ -104,17 +124,10 @@ public class ResponseEntityTests { public void postEntity() throws Exception { this.client.post().uri("/persons") .exchange(Mono.just(new Person("John")), Person.class) - .assertStatus().isCreated() - .assertHeader("location").isEqualTo("/persons/John").and() - .assertBody().isEmpty(); - } - - @Test - public void entityConsumer() throws Exception { - this.client.get().uri("/persons/John") - .exchange() - .assertStatus().isOk() - .assertEntity(Person.class).consume(p -> assertEquals(new Person("John"), p)); + .expectNoBody() + .assertThat() + .status().isCreated() + .header().valueEquals("location", "/persons/John"); } diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/bind/ApplicationContextTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/bind/ApplicationContextTests.java index 9cfefa2911..3bf3fc0475 100644 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/bind/ApplicationContextTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/bind/ApplicationContextTests.java @@ -51,8 +51,10 @@ public class ApplicationContextTests { public void test() throws Exception { this.client.get().uri("/test") .exchange() - .assertStatus().isOk() - .assertEntity(String .class).isEqualTo("It works!"); + .decodeEntity(String.class) + .assertThat() + .status().isOk() + .bodyEquals("It works!"); } diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/bind/ControllerTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/bind/ControllerTests.java index 542e6aacb6..a39a558d38 100644 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/bind/ControllerTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/bind/ControllerTests.java @@ -43,8 +43,10 @@ public class ControllerTests { public void test() throws Exception { this.client.get().uri("/test") .exchange() - .assertStatus().isOk() - .assertEntity(String .class).isEqualTo("It works!"); + .decodeEntity(String.class) + .assertThat() + .status().isOk() + .bodyEquals("It works!"); } diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/bind/HttpServerTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/bind/HttpServerTests.java index aede9ed651..ef3de7f936 100644 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/bind/HttpServerTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/bind/HttpServerTests.java @@ -68,8 +68,10 @@ public class HttpServerTests { public void test() throws Exception { this.client.get().uri("/test") .exchange() - .assertStatus().isOk() - .assertEntity(String .class).isEqualTo("It works!"); + .decodeEntity(String.class) + .assertThat() + .status().isOk() + .bodyEquals("It works!"); } } diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/bind/RouterFunctionTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/bind/RouterFunctionTests.java index 49c1dd6cf8..54161b4377 100644 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/bind/RouterFunctionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/bind/RouterFunctionTests.java @@ -49,8 +49,10 @@ public class RouterFunctionTests { public void test() throws Exception { this.testClient.get().uri("/test") .exchange() - .assertStatus().isOk() - .assertEntity(String .class).isEqualTo("It works!"); + .decodeEntity(String.class) + .assertThat() + .status().isOk() + .bodyEquals("It works!"); } }