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 5535cadc2f9..96effcdfa5d 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.Arrays; import java.util.List; import java.util.Map; import java.util.Optional; @@ -26,6 +27,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; import java.util.function.Function; +import java.util.stream.Collectors; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; @@ -34,10 +36,10 @@ 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.HttpMethod; 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; @@ -46,6 +48,9 @@ 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.test.util.AssertionErrors.assertEquals; +import static org.springframework.test.util.AssertionErrors.assertTrue; +import static org.springframework.test.util.AssertionErrors.fail; import static org.springframework.web.reactive.function.BodyExtractors.toDataBuffers; import static org.springframework.web.reactive.function.BodyExtractors.toFlux; import static org.springframework.web.reactive.function.BodyExtractors.toMono; @@ -242,88 +247,301 @@ class DefaultWebTestClient implements WebTestClient { @Override public ResponseSpec exchange() { - return new DefaultResponseSpec(this.requestId, this.headerSpec.exchange()); + return createResponseSpec(this.headerSpec.exchange()); } @Override public ResponseSpec exchange(BodyInserter inserter) { - return new DefaultResponseSpec(this.requestId, this.headerSpec.exchange(inserter)); + return createResponseSpec(this.headerSpec.exchange(inserter)); } @Override public > ResponseSpec exchange(S publisher, Class elementClass) { - return new DefaultResponseSpec(this.requestId, this.headerSpec.exchange(publisher, elementClass)); + return createResponseSpec(this.headerSpec.exchange(publisher, elementClass)); + } + + protected DefaultResponseSpec createResponseSpec(Mono responseMono) { + ClientResponse response = responseMono.block(getTimeout()); + WiretapConnector.Info info = connectorListener.retrieveRequest(this.requestId); + HttpMethod method = info.getMethod(); + URI url = info.getUrl(); + HttpHeaders headers = info.getRequestHeaders(); + ExchangeResult> result = ExchangeResult.fromResponse(method, url, headers, response); + return new DefaultResponseSpec(result, response); + } + + } + + private abstract class ResponseSpecSupport { + + private final ExchangeResult> exchangeResult; + + private final ClientResponse response; + + + public ResponseSpecSupport(ExchangeResult> result, ClientResponse response) { + this.exchangeResult = result; + this.response = response; + } + + public ResponseSpecSupport(ResponseSpecSupport responseSpec) { + this.exchangeResult = responseSpec.getExchangeResult(); + this.response = responseSpec.getResponse(); + } + + + protected ExchangeResult> getExchangeResult() { + return this.exchangeResult; + } + + protected ClientResponse getResponse() { + return this.response; + } + + protected HttpHeaders getResponseHeaders() { + return getExchangeResult().getResponseHeaders(); + } + + protected ExchangeResult createResultWithDecodedBody(T body) { + return ExchangeResult.withDecodedBody(this.exchangeResult, body); + } + + } + + private class DefaultResponseSpec extends ResponseSpecSupport implements ResponseSpec { + + + public DefaultResponseSpec(ExchangeResult> result, ClientResponse response) { + super(result, response); + } + + + @Override + public StatusAssertions expectStatus() { + return new StatusAssertions(getResponse().statusCode(), this); + } + + @Override + public HeaderAssertions expectHeader() { + return new HeaderAssertions(getResponseHeaders(), this); + } + + @Override + public BodySpec expectBody() { + return new DefaultBodySpec(this); + } + + @Override + public ElementBodySpec expectBody(Class elementType) { + return expectBody(ResolvableType.forClass(elementType)); + } + + @Override + public ElementBodySpec expectBody(ResolvableType elementType) { + return new DefaultElementBodySpec(this, elementType); + } + + @Override + public ResponseSpec consumeWith(Consumer>> consumer) { + consumer.accept(getExchangeResult()); + return this; + } + + @Override + public ExchangeResult> returnResult() { + return getExchangeResult(); } } - private class DefaultResponseSpec implements ResponseSpec { - - private final String requestId; - - private final Mono responseMono; + private class DefaultBodySpec extends ResponseSpecSupport implements BodySpec { - public DefaultResponseSpec(String requestId, Mono responseMono) { - this.requestId = requestId; - this.responseMono = responseMono; + public DefaultBodySpec(ResponseSpecSupport responseSpec) { + super(responseSpec); } @Override - public ExchangeResult decodeEntity(Class entityClass) { - return decodeEntity(ResolvableType.forClass(entityClass)); + public ExchangeResult isEmpty() { + DataBuffer buffer = getResponse().body(toDataBuffers()).blockFirst(getTimeout()); + assertTrue("Expected empty body", buffer == null); + return createResultWithDecodedBody(null); } @Override - public ExchangeResult> decodeAndCollect(Class elementClass) { - return decodeAndCollect(ResolvableType.forClass(elementClass)); + public MapBodySpec map(Class keyType, Class valueType) { + return map(ResolvableType.forClass(keyType), ResolvableType.forClass(valueType)); } @Override - public ExchangeResult> decodeFlux(Class elementClass) { - return decodeFlux(ResolvableType.forClass(elementClass)); + public MapBodySpec map(ResolvableType keyType, ResolvableType valueType) { + return new DefaultMapBodySpec(this, keyType, valueType); + } + } + + private class DefaultMapBodySpec extends ResponseSpecSupport implements MapBodySpec { + + private final Map body; + + + public DefaultMapBodySpec(ResponseSpecSupport spec, ResolvableType keyType, ResolvableType valueType) { + super(spec); + ResolvableType mapType = ResolvableType.forClassWithGenerics(Map.class, keyType, valueType); + this.body = (Map) spec.getResponse().body(toMono(mapType)).block(getTimeout()); + } + + + @Override + public ExchangeResult> isEqualTo(Map expected) { + return returnResult(); } @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()); + public MapBodySpec hasSize(int size) { + assertEquals("Response body map size", size, this.body.size()); + return this; } @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()); + public MapBodySpec contains(Object key, Object value) { + assertEquals("Response body map value for key " + key, value, this.body.get(key)); + return this; } @Override - public ExchangeResult> decodeFlux(ResolvableType elementType) { - return this.responseMono.map(response -> { - Flux entityFlux = response.body(toFlux(elementType)); - return createTestExchange(entityFlux, response); - }).block(getTimeout()); + public MapBodySpec containsKeys(Object... keys) { + List missing = Arrays.stream(keys) + .filter(key -> !this.body.containsKey(key)) + .collect(Collectors.toList()); + if (!missing.isEmpty()) { + fail("Response body map does not contain keys " + Arrays.toString(keys)); + } + return this; } @Override - public 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()); + public MapBodySpec containsValues(Object... values) { + List missing = Arrays.stream(values) + .filter(value -> !this.body.containsValue(value)) + .collect(Collectors.toList()); + if (!missing.isEmpty()) { + fail("Response body map does not contain values " + Arrays.toString(values)); + } + return this; } - private ExchangeResult createTestExchange(T body, ClientResponse response) { - WiretapConnector.Info wiretapInfo = connectorListener.retrieveRequest(requestId); - ClientHttpRequest request = wiretapInfo.getRequest(); - return new ExchangeResult( - request.getMethod(), request.getURI(), request.getHeaders(), - response.statusCode(), response.headers().asHttpHeaders(), body); + @Override + @SuppressWarnings("unchecked") + public ExchangeResult> returnResult() { + return createResultWithDecodedBody((Map) this.body); + } + } + + private class DefaultElementBodySpec extends ResponseSpecSupport implements ElementBodySpec { + + private final ResolvableType elementType; + + + public DefaultElementBodySpec(ResponseSpecSupport spec, ResolvableType elementType) { + super(spec); + this.elementType = elementType; + } + + + @Override + public SingleValueBodySpec value() { + return new DefaultSingleValueBodySpec(this, this.elementType); + } + + @Override + public ListBodySpec list() { + return new DefaultListBodySpec(this, this.elementType, -1); + } + + @Override + public ListBodySpec list(int elementCount) { + return new DefaultListBodySpec(this, this.elementType, elementCount); + } + + @Override + public ExchangeResult> returnResult() { + Flux flux = getResponse().body(toFlux(this.elementType)); + return createResultWithDecodedBody(flux); + } + } + + private class DefaultSingleValueBodySpec extends ResponseSpecSupport + implements SingleValueBodySpec { + + private final Object body; + + + public DefaultSingleValueBodySpec(ResponseSpecSupport spec, ResolvableType elementType) { + super(spec); + this.body = getResponse().body(toMono(elementType)).block(getTimeout()); + } + + + @Override + public ExchangeResult isEqualTo(Object expected) { + assertEquals("Response body", expected, this.body); + return returnResult(); + } + + @Override + @SuppressWarnings("unchecked") + public ExchangeResult returnResult() { + return createResultWithDecodedBody((T) this.body); + } + } + + private class DefaultListBodySpec extends ResponseSpecSupport + implements ListBodySpec { + + private final List body; + + + public DefaultListBodySpec(ResponseSpecSupport spec, ResolvableType elementType, int elementCount) { + super(spec); + Flux flux = getResponse().body(toFlux(elementType)); + if (elementCount >= 0) { + flux = flux.take(elementCount); + } + this.body = flux.collectList().block(getTimeout()); + } + + + @Override + public ExchangeResult> isEqualTo(List expected) { + assertEquals("Response body", expected, this.body); + return returnResult(); + } + + @Override + public ListBodySpec hasSize(int size) { + return this; + } + + @Override + public ListBodySpec contains(Object... elements) { + List elementList = Arrays.asList(elements); + String message = "Response body does not contain " + elementList; + assertTrue(message, this.body.containsAll(elementList)); + return this; + } + + @Override + public ListBodySpec doesNotContain(Object... elements) { + List elementList = Arrays.asList(elements); + String message = "Response body should have contained " + elementList; + assertTrue(message, !this.body.containsAll(Arrays.asList(elements))); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public ExchangeResult> returnResult() { + return createResultWithDecodedBody((List) this.body); } } @@ -346,7 +564,7 @@ class DefaultWebTestClient implements WebTestClient { @Override public void accept(WiretapConnector.Info info) { - Optional.ofNullable(info.getRequest().getHeaders().getFirst(REQUEST_ID_HEADER_NAME)) + Optional.ofNullable(info.getRequestHeaders().getFirst(REQUEST_ID_HEADER_NAME)) .ifPresent(id -> this.exchanges.put(id, info)); } diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ExchangeResult.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/ExchangeResult.java index 72097b2d32d..03b5394d13a 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ExchangeResult.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/ExchangeResult.java @@ -18,21 +18,21 @@ package org.springframework.test.web.reactive.server; import java.net.URI; import java.util.stream.Collectors; +import reactor.core.publisher.Flux; + +import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; +import org.springframework.web.reactive.function.client.ClientResponse; + +import static org.springframework.web.reactive.function.BodyExtractors.toDataBuffers; /** - * Container for request and response details including the decoded response - * body from an exchange performed through {@link WebTestClient}. + * Container for request and response details 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 + * @param the type of the response body * * @author Rossen Stoyanchev * @since 5.0 @@ -52,8 +52,11 @@ public class ExchangeResult { private final T responseBody; - public ExchangeResult(HttpMethod method, URI url, HttpHeaders requestHeaders, - HttpStatus status, HttpHeaders responseHeaders, T responseBody) { + /** + * Package private constructor. + */ + ExchangeResult(HttpMethod method, URI url, HttpHeaders requestHeaders, HttpStatus status, + HttpHeaders responseHeaders, T responseBody) { this.method = method; this.url = url; @@ -64,25 +67,17 @@ public class ExchangeResult { } - /** - * Provides access to built-in assertions on the response. - */ - public ResponseAssertions assertThat() { - return new ResponseAssertions(this); - } - - /** * Return the request method of the exchange. */ - public HttpMethod getRequestMethod() { + public HttpMethod getMethod() { return this.method; } /** * Return the URL of the exchange. */ - public URI getRequestUrl() { + public URI getUrl() { return this.url; } @@ -96,7 +91,7 @@ public class ExchangeResult { /** * Return the response status. */ - public HttpStatus getResponseStatus() { + public HttpStatus getStatus() { return this.status; } @@ -115,11 +110,32 @@ public class ExchangeResult { } + /** + * Create an instance from a ClientResponse (body not yet consumed). + */ + static ExchangeResult> fromResponse(HttpMethod method, URI url, + HttpHeaders requestHeaders, ClientResponse response) { + + HttpStatus status = response.statusCode(); + HttpHeaders responseHeaders = response.headers().asHttpHeaders(); + Flux body = response.body(toDataBuffers()); + return new ExchangeResult<>(method, url, requestHeaders, status, responseHeaders, body); + } + + /** + * Re-create the result with a generic type matching the decoded body. + */ + static ExchangeResult withDecodedBody(ExchangeResult result, T body) { + return new ExchangeResult<>(result.getMethod(), result.getUrl(), result.getRequestHeaders(), + result.getStatus(), result.getResponseHeaders(), body); + } + + @Override public String toString() { HttpStatus status = this.status; return "\n\n" + - formatValue("Request", this.method + " " + getRequestUrl()) + + formatValue("Request", this.method + " " + getUrl()) + formatValue("Status", status + " " + status.getReasonPhrase()) + formatHeading("Response Headers") + formatHeaders(this.responseHeaders) + diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/HeaderAssertions.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/HeaderAssertions.java index be32eed1b12..de1599deea8 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/HeaderAssertions.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/HeaderAssertions.java @@ -35,68 +35,68 @@ import static org.springframework.test.util.AssertionErrors.assertTrue; * @since 5.0 * @see ResponseAssertions#header() */ -public class HeaderAssertions { - - private final ResponseAssertions resultAssertions; +public class HeaderAssertions { private final HttpHeaders headers; + private final WebTestClient.ResponseSpec responseSpec; - public HeaderAssertions(HttpHeaders headers, ResponseAssertions resultAssertions) { - this.resultAssertions = resultAssertions; + + public HeaderAssertions(HttpHeaders headers, WebTestClient.ResponseSpec responseSpec) { this.headers = headers; + this.responseSpec = responseSpec; } - public ResponseAssertions valueEquals(String headerName, String... values) { + public WebTestClient.ResponseSpec valueEquals(String headerName, String... values) { List actual = this.headers.get(headerName); assertEquals("Response header [" + headerName + "]", Arrays.asList(values), actual); - return this.resultAssertions; + return this.responseSpec; } - public ResponseAssertions valueMatches(String headerName, String pattern) { + public WebTestClient.ResponseSpec 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; + return this.responseSpec; } - public ResponseAssertions cacheControlEquals(CacheControl cacheControl) { + public WebTestClient.ResponseSpec cacheControlEquals(CacheControl cacheControl) { String actual = this.headers.getCacheControl(); assertEquals("Response header Cache-Control", cacheControl.getHeaderValue(), actual); - return this.resultAssertions; + return this.responseSpec; } - public ResponseAssertions contentDispositionEquals(ContentDisposition contentDisposition) { + public WebTestClient.ResponseSpec contentDispositionEquals(ContentDisposition contentDisposition) { ContentDisposition actual = this.headers.getContentDisposition(); assertEquals("Response header Content-Disposition", contentDisposition, actual); - return this.resultAssertions; + return this.responseSpec; } - public ResponseAssertions contentLengthEquals(long contentLength) { + public WebTestClient.ResponseSpec contentLengthEquals(long contentLength) { long actual = this.headers.getContentLength(); assertEquals("Response header Content-Length", contentLength, actual); - return this.resultAssertions; + return this.responseSpec; } - public ResponseAssertions contentTypeEquals(MediaType mediaType) { + public WebTestClient.ResponseSpec contentTypeEquals(MediaType mediaType) { MediaType actual = this.headers.getContentType(); assertEquals("Response header Content-Type", mediaType, actual); - return this.resultAssertions; + return this.responseSpec; } - public ResponseAssertions expiresEquals(int expires) { + public WebTestClient.ResponseSpec expiresEquals(int expires) { long actual = this.headers.getExpires(); assertEquals("Response header Expires", expires, actual); - return this.resultAssertions; + return this.responseSpec; } - public ResponseAssertions lastModifiedEquals(int lastModified) { + public WebTestClient.ResponseSpec lastModifiedEquals(int lastModified) { long actual = this.headers.getLastModified(); assertEquals("Response header Last-Modified", lastModified, actual); - return this.resultAssertions; + return this.responseSpec; } } diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/LoggingActions.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/LoggingActions.java deleted file mode 100644 index 7ce29c0b9ff..00000000000 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/LoggingActions.java +++ /dev/null @@ -1,110 +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.io.IOException; -import java.io.OutputStream; -import java.io.PrintWriter; -import java.io.Writer; -import java.util.function.BiConsumer; -import java.util.function.Predicate; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Provides options for logging diagnostic information about the exchange. - * - * @author Rossen Stoyanchev - * @since 5.0 - * @see ResponseAssertions#log() - */ -public class LoggingActions { - - private static Log logger = LogFactory.getLog(LoggingActions.class); - - private final ResponseAssertions resultAssertions; - - private final ExchangeResult exchange; - - - public LoggingActions(ExchangeResult exchange, ResponseAssertions resultAssertions) { - this.resultAssertions = resultAssertions; - this.exchange = exchange; - } - - - /** - * Log with {@link System#out}. - */ - public ResponseAssertions toConsole() { - return toOutputStream(System.out); - } - - /** - * Log with a given {@link OutputStream}. - */ - public ResponseAssertions toOutputStream(OutputStream stream) { - return toWriter(new PrintWriter(stream, true)); - } - - /** - * Log with a given {@link Writer}. - */ - public ResponseAssertions toWriter(Writer writer) { - try { - writer.write(getOutput()); - } - catch (IOException ex) { - throw new IllegalStateException("Failed to print exchange info", ex); - } - return this.resultAssertions; - } - - /** - * Log if TRACE level logging is enabled. - */ - public ResponseAssertions ifTraceEnabled() { - return doLog(Log::isTraceEnabled, Log::trace); - } - - /** - * Log if DEBUG level logging is enabled. - */ - public ResponseAssertions ifDebugEnabled() { - return doLog(Log::isDebugEnabled, Log::debug); - } - - /** - * Log if INFO level logging is enabled. - */ - public ResponseAssertions ifInfoEnabled() { - return doLog(Log::isInfoEnabled, Log::info); - } - - - private ResponseAssertions doLog(Predicate predicate, BiConsumer consumer) { - if (predicate.test(logger)) { - consumer.accept(logger, getOutput()); - } - return this.resultAssertions; - } - - private String getOutput() { - return this.resultAssertions.toString(); - } - -} 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 deleted file mode 100644 index 19a4ccd4979..00000000000 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ResponseAssertions.java +++ /dev/null @@ -1,66 +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 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/StatusAssertions.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/StatusAssertions.java index 8f7d04cbf5f..10c8489fc94 100644 --- 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 @@ -27,174 +27,174 @@ import static org.springframework.test.util.AssertionErrors.assertEquals; * @see ResponseAssertions#status() */ @SuppressWarnings("unused") -public class StatusAssertions { - - private final ResponseAssertions resultAssertions; +public class StatusAssertions { private final HttpStatus httpStatus; + private final WebTestClient.ResponseSpec responseSpec; - StatusAssertions(HttpStatus status, ResponseAssertions exchangeActions) { - this.resultAssertions = exchangeActions; + + StatusAssertions(HttpStatus status, WebTestClient.ResponseSpec responseSpec) { this.httpStatus = status; + this.responseSpec = responseSpec; } /** * Assert the response status as an {@link HttpStatus}. */ - public ResponseAssertions isEqualTo(HttpStatus status) { + public WebTestClient.ResponseSpec isEqualTo(HttpStatus status) { assertEquals("Response status", status, this.httpStatus); - return this.resultAssertions; + return this.responseSpec; } /** * Assert the response status as an integer. */ - public ResponseAssertions isEqualTo(int status) { + public WebTestClient.ResponseSpec isEqualTo(int status) { assertEquals("Response status", status, this.httpStatus.value()); - return this.resultAssertions; + return this.responseSpec; } /** * Assert the response status code is {@code HttpStatus.OK} (200). */ - public ResponseAssertions isOk() { + public WebTestClient.ResponseSpec isOk() { assertEquals("Status", HttpStatus.OK, this.httpStatus); - return this.resultAssertions; + return this.responseSpec; } /** * Assert the response status code is {@code HttpStatus.CREATED} (201). */ - public ResponseAssertions isCreated() { + public WebTestClient.ResponseSpec isCreated() { assertEquals("Status", HttpStatus.CREATED, this.httpStatus); - return this.resultAssertions; + return this.responseSpec; } /** * Assert the response status code is {@code HttpStatus.ACCEPTED} (202). */ - public ResponseAssertions isAccepted() { + public WebTestClient.ResponseSpec isAccepted() { assertEquals("Status", HttpStatus.ACCEPTED, this.httpStatus); - return this.resultAssertions; + return this.responseSpec; } /** * Assert the response status code is {@code HttpStatus.NO_CONTENT} (204). */ - public ResponseAssertions isNoContent() { + public WebTestClient.ResponseSpec isNoContent() { assertEquals("Status", HttpStatus.NO_CONTENT, this.httpStatus); - return this.resultAssertions; + return this.responseSpec; } /** * Assert the response status code is {@code HttpStatus.FOUND} (302). */ - public ResponseAssertions isFound() { + public WebTestClient.ResponseSpec isFound() { assertEquals("Status", HttpStatus.FOUND, this.httpStatus); - return this.resultAssertions; + return this.responseSpec; } /** * Assert the response status code is {@code HttpStatus.SEE_OTHER} (303). */ - public ResponseAssertions isSeeOther() { + public WebTestClient.ResponseSpec isSeeOther() { assertEquals("Status", HttpStatus.SEE_OTHER, this.httpStatus); - return this.resultAssertions; + return this.responseSpec; } /** * Assert the response status code is {@code HttpStatus.NOT_MODIFIED} (304). */ - public ResponseAssertions isNotModified() { + public WebTestClient.ResponseSpec isNotModified() { assertEquals("Status", HttpStatus.NOT_MODIFIED, this.httpStatus); - return this.resultAssertions; + return this.responseSpec; } /** * Assert the response status code is {@code HttpStatus.TEMPORARY_REDIRECT} (307). */ - public ResponseAssertions isTemporaryRedirect() { + public WebTestClient.ResponseSpec isTemporaryRedirect() { assertEquals("Status", HttpStatus.TEMPORARY_REDIRECT, this.httpStatus); - return this.resultAssertions; + return this.responseSpec; } /** * Assert the response status code is {@code HttpStatus.PERMANENT_REDIRECT} (308). */ - public ResponseAssertions isPermanentRedirect() { + public WebTestClient.ResponseSpec isPermanentRedirect() { assertEquals("Status", HttpStatus.PERMANENT_REDIRECT, this.httpStatus); - return this.resultAssertions; + return this.responseSpec; } /** * Assert the response status code is {@code HttpStatus.BAD_REQUEST} (400). */ - public ResponseAssertions isBadRequest() { + public WebTestClient.ResponseSpec isBadRequest() { assertEquals("Status", HttpStatus.BAD_REQUEST, this.httpStatus); - return this.resultAssertions; + return this.responseSpec; } /** * Assert the response status code is {@code HttpStatus.NOT_FOUND} (404). */ - public ResponseAssertions isNotFound() { + public WebTestClient.ResponseSpec isNotFound() { assertEquals("Status", HttpStatus.NOT_FOUND, this.httpStatus); - return this.resultAssertions; + return this.responseSpec; } /** * Assert the response error message. */ - public ResponseAssertions reasonEquals(String reason) { + public WebTestClient.ResponseSpec reasonEquals(String reason) { assertEquals("Response status reason", reason, this.httpStatus.getReasonPhrase()); - return this.resultAssertions; + return this.responseSpec; } /** * Assert the response status code is in the 1xx range. */ - public ResponseAssertions is1xxInformational() { + public WebTestClient.ResponseSpec is1xxInformational() { String message = "Range for response status value " + this.httpStatus; assertEquals(message, HttpStatus.Series.INFORMATIONAL, this.httpStatus.series()); - return this.resultAssertions; + return this.responseSpec; } /** * Assert the response status code is in the 2xx range. */ - public ResponseAssertions is2xxSuccessful() { + public WebTestClient.ResponseSpec is2xxSuccessful() { String message = "Range for response status value " + this.httpStatus; assertEquals(message, HttpStatus.Series.SUCCESSFUL, this.httpStatus.series()); - return this.resultAssertions; + return this.responseSpec; } /** * Assert the response status code is in the 3xx range. */ - public ResponseAssertions is3xxRedirection() { + public WebTestClient.ResponseSpec is3xxRedirection() { String message = "Range for response status value " + this.httpStatus; assertEquals(message, HttpStatus.Series.REDIRECTION, this.httpStatus.series()); - return this.resultAssertions; + return this.responseSpec; } /** * Assert the response status code is in the 4xx range. */ - public ResponseAssertions is4xxClientError() { + public WebTestClient.ResponseSpec is4xxClientError() { String message = "Range for response status value " + this.httpStatus; assertEquals(message, HttpStatus.Series.CLIENT_ERROR, this.httpStatus.series()); - return this.resultAssertions; + return this.responseSpec; } /** * Assert the response status code is in the 5xx range. */ - public ResponseAssertions is5xxServerError() { + public WebTestClient.ResponseSpec is5xxServerError() { String message = "Range for response status value " + this.httpStatus; assertEquals(message, HttpStatus.Series.SERVER_ERROR, this.httpStatus.series()); - return this.resultAssertions; + return this.responseSpec; } } 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 5b0d283e7ef..b31af720dc8 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 @@ -29,6 +29,7 @@ import reactor.core.publisher.Flux; import org.springframework.context.ApplicationContext; import org.springframework.core.ResolvableType; +import org.springframework.core.io.buffer.DataBuffer; import org.springframework.format.FormatterRegistry; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -447,72 +448,191 @@ public interface WebTestClient { } /** - * Specification for decoding the response of an exchange. + * Specification for expectations on the response. */ 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 + * Assertions on the response status. */ - ExchangeResult decodeEntity(Class entityClass); + StatusAssertions expectStatus(); /** - * 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[]) + * Assertions on the response headers. */ - ExchangeResult decodeEntity(ResolvableType entityType); + HeaderAssertions expectHeader(); /** - * 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 + * Assertions on the response body. */ - ExchangeResult> decodeAndCollect(Class elementClass); + BodySpec expectBody(); /** - * 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[]) + * Assertions on the response body where the body is to be decoded to + * one or more elements of the given type. */ - ExchangeResult> decodeAndCollect(ResolvableType elementType); + ElementBodySpec expectBody(Class 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 + * Alternative to {@link #expectBody(Class)} for generic types. */ - ExchangeResult> decodeFlux(Class elementClass); + ElementBodySpec expectBody(ResolvableType elementType); /** - * 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[]) + * Consume the result of the exchange and continue with expectations. */ - ExchangeResult> decodeFlux(ResolvableType elementType); + ResponseSpec consumeWith(Consumer>> consumer); /** - * Consume the response and verify there is no content. + * Return a container for the result of the exchange with the body + * not yet decoded nor consumed. + */ + ExchangeResult> returnResult(); + } + + /** + * Specification for expectations on the body of the response. + */ + interface BodySpec { + + /** + * Consume the body and verify it is empty. * @return container for the result of the exchange */ - ExchangeResult expectNoBody(); + ExchangeResult isEmpty(); + + /** + * Decode the response body as a Map with the given key and value type. + */ + MapBodySpec map(Class keyType, Class valueType); + + /** + * Alternative to {@link #map(Class, Class)} for generic types. + */ + MapBodySpec map(ResolvableType keyType, ResolvableType valueType); } + /** + * Specification for expectations on the body of the response decoded as a map. + */ + interface MapBodySpec { + + /** + * Assert the decoded body is equal to the given list of elements. + */ + ExchangeResult> isEqualTo(Map expected); + + /** + * Assert the decoded map has the given size. + * @param size the expected size + */ + MapBodySpec hasSize(int size); + + /** + * Assert the decoded map contains the given key value pair. + * @param key the key to check + * @param value the value to check + */ + MapBodySpec contains(Object key, Object value); + + /** + * Assert the decoded map contains the given keys. + * @param keys the keys to check + */ + MapBodySpec containsKeys(Object... keys); + + /** + * Assert the decoded map contains the given values. + * @param values the keys to check + */ + MapBodySpec containsValues(Object... values); + + /** + * Return a container for the result of the exchange. + */ + ExchangeResult> returnResult(); + } + + /** + * Specification for expectations on the body of the response to be decoded + * as one or more elements of a specific type. + */ + interface ElementBodySpec { + + /** + * Decode the response as a single element. + */ + SingleValueBodySpec value(); + + /** + * Decode the response as a Flux of objects and collect it to a list. + */ + ListBodySpec list(); + + /** + * Decode the response as a Flux of objects consuming only the specified + * number of elements. + */ + ListBodySpec list(int elementCount); + + /** + * Decode the response as a Flux of objects and return a container for + * the result where the response body not yet consumed. + */ + ExchangeResult> returnResult(); + } + + /** + * Specification for expectations on the body of the response decoded as a + * single element of a specific type. + */ + interface SingleValueBodySpec { + + /** + * Assert the decoded body is equal to the given value. + */ + ExchangeResult isEqualTo(Object expected); + + /** + * Return a container for the result of the exchange. + */ + ExchangeResult returnResult(); + } + + /** + * Specification for expectations on the body of the response decoded as a + * list of elements of a specific type. + */ + interface ListBodySpec { + + /** + * Assert the decoded body is equal to the given list of elements. + */ + ExchangeResult> isEqualTo(List expected); + + /** + * Assert the decoded list of values is of the given size. + * @param size the expected size + */ + ListBodySpec hasSize(int size); + + /** + * Assert the decoded list of values contains the given elements. + * @param elements the elements to check + */ + ListBodySpec contains(Object... elements); + + /** + * Assert the decoded list of values does not contain the given elements. + * @param elements the elements to check + */ + ListBodySpec doesNotContain(Object... elements); + + /** + * Return a container for the result of the exchange. + */ + ExchangeResult> returnResult(); + } + } diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WiretapConnector.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WiretapConnector.java index d5497750f8f..98170a17372 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WiretapConnector.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WiretapConnector.java @@ -24,6 +24,7 @@ import java.util.function.Function; import reactor.core.publisher.Mono; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.client.reactive.ClientHttpConnector; import org.springframework.http.client.reactive.ClientHttpRequest; @@ -79,19 +80,33 @@ class WiretapConnector implements ClientHttpConnector { public static class Info { - private final ClientHttpRequest request; + private final HttpMethod method; + + private final URI url; + + private final HttpHeaders requestHeaders; private final ClientHttpResponse response; public Info(ClientHttpRequest request, ClientHttpResponse response) { - this.request = request; + this.method = request.getMethod(); + this.url = request.getURI(); + this.requestHeaders = request.getHeaders(); this.response = response; } - public ClientHttpRequest getRequest() { - return this.request; + public HttpMethod getMethod() { + return this.method; + } + + public URI getUrl() { + return this.url; + } + + public HttpHeaders getRequestHeaders() { + return this.requestHeaders; } public ClientHttpResponse getResponse() { diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/WiretapConnectorTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/WiretapConnectorTests.java index 622379ec44d..cfebc34d207 100644 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/WiretapConnectorTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/WiretapConnectorTests.java @@ -33,6 +33,7 @@ import org.springframework.web.reactive.function.client.ClientRequest; import org.springframework.web.reactive.function.client.ExchangeFunction; import org.springframework.web.reactive.function.client.ExchangeFunctions; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; @@ -60,7 +61,8 @@ public class WiretapConnectorTests { WiretapConnector.Info info = infoRef.get(); assertNotNull(info); - assertSame(request, info.getRequest()); + assertEquals(HttpMethod.GET, info.getMethod()); + assertEquals("/test", info.getUrl().toString()); assertSame(response, info.getResponse()); } 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 987fcf0eddf..d8a2f86c238 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 @@ -44,18 +44,16 @@ public class ErrorTests { public void notFound() throws Exception { this.client.get().uri("/invalid") .exchange() - .expectNoBody() - .assertThat() - .status().isNotFound(); + .expectStatus().isNotFound() + .expectBody().isEmpty(); } @Test public void serverException() throws Exception { this.client.get().uri("/server-error") .exchange() - .expectNoBody() - .assertThat() - .status().isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); + .expectStatus().isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR) + .expectBody().isEmpty(); } 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 efe554925e8..9f901d83fc0 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 @@ -49,20 +49,16 @@ public class HeaderTests { public void requestResponseHeaderPair() throws Exception { this.client.get().uri("/request-response-pair").header("h1", "in") .exchange() - .expectNoBody() - .assertThat() - .status().isOk() - .header().valueEquals("h1", "in-out"); + .expectStatus().isOk() + .expectHeader().valueEquals("h1", "in-out"); } @Test public void headerMultivalue() throws Exception { this.client.get().uri("/multivalue") .exchange() - .expectNoBody() - .assertThat() - .status().isOk() - .header().valueEquals("h1", "v1", "v2", "v3"); + .expectStatus().isOk() + .expectHeader().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 c3f0e598c84..df0f80cdd46 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 @@ -18,6 +18,7 @@ package org.springframework.test.web.reactive.server.samples; import java.net.URI; import java.util.Arrays; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import com.fasterxml.jackson.annotation.JsonCreator; @@ -28,7 +29,6 @@ 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; @@ -65,22 +65,22 @@ public class ResponseEntityTests { public void entity() throws Exception { this.client.get().uri("/persons/John") .exchange() - .decodeEntity(Person.class) - .assertThat() - .status().isOk() - .header().contentTypeEquals(MediaType.APPLICATION_JSON_UTF8) - .bodyEquals(new Person("John")); + .expectStatus().isOk() + .expectHeader().contentTypeEquals(MediaType.APPLICATION_JSON_UTF8) + .expectBody(Person.class).value().isEqualTo(new Person("John")); } @Test public void entityList() throws Exception { + + List expected = Arrays.asList( + new Person("Jane"), new Person("Jason"), new Person("John")); + this.client.get().uri("/persons") .exchange() - .decodeAndCollect(Person.class) - .assertThat() - .status().isOk() - .header().contentTypeEquals(MediaType.APPLICATION_JSON_UTF8) - .bodyEquals(Arrays.asList(new Person("Jane"), new Person("Jason"), new Person("John"))); + .expectStatus().isOk() + .expectHeader().contentTypeEquals(MediaType.APPLICATION_JSON_UTF8) + .expectBody(Person.class).list().isEqualTo(expected); } @Test @@ -93,10 +93,9 @@ public class ResponseEntityTests { this.client.get().uri("/persons?map=true") .exchange() - .decodeEntity(ResolvableType.forClassWithGenerics(Map.class, String.class, Person.class)) - .assertThat() - .status().isOk() - .bodyEquals(map); + .expectStatus().isOk() + .expectBody() + .map(String.class, Person.class).isEqualTo(map); } @Test @@ -106,11 +105,10 @@ public class ResponseEntityTests { .uri("/persons") .accept(TEXT_EVENT_STREAM) .exchange() - .decodeFlux(Person.class); - - result.assertThat() - .status().isOk() - .header().contentTypeEquals(TEXT_EVENT_STREAM); + .expectStatus().isOk() + .expectHeader().contentTypeEquals(TEXT_EVENT_STREAM) + .expectBody(Person.class) + .returnResult(); StepVerifier.create(result.getResponseBody()) .expectNext(new Person("N0"), new Person("N1"), new Person("N2")) @@ -124,10 +122,9 @@ public class ResponseEntityTests { public void postEntity() throws Exception { this.client.post().uri("/persons") .exchange(Mono.just(new Person("John")), Person.class) - .expectNoBody() - .assertThat() - .status().isCreated() - .header().valueEquals("location", "/persons/John"); + .expectStatus().isCreated() + .expectHeader().valueEquals("location", "/persons/John") + .expectBody().isEmpty(); } 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 3bf3fc04756..c48957bdb48 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,10 +51,8 @@ public class ApplicationContextTests { public void test() throws Exception { this.client.get().uri("/test") .exchange() - .decodeEntity(String.class) - .assertThat() - .status().isOk() - .bodyEquals("It works!"); + .expectStatus().isOk() + .expectBody(String.class).value().isEqualTo("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 a39a558d38b..9ab806f5362 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,10 +43,8 @@ public class ControllerTests { public void test() throws Exception { this.client.get().uri("/test") .exchange() - .decodeEntity(String.class) - .assertThat() - .status().isOk() - .bodyEquals("It works!"); + .expectStatus().isOk() + .expectBody(String.class).value().isEqualTo("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 ef3de7f9364..e91ae8c0d0f 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,10 +68,8 @@ public class HttpServerTests { public void test() throws Exception { this.client.get().uri("/test") .exchange() - .decodeEntity(String.class) - .assertThat() - .status().isOk() - .bodyEquals("It works!"); + .expectStatus().isOk() + .expectBody(String.class).value().isEqualTo("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 54161b4377f..3f06f33fd17 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,10 +49,8 @@ public class RouterFunctionTests { public void test() throws Exception { this.testClient.get().uri("/test") .exchange() - .decodeEntity(String.class) - .assertThat() - .status().isOk() - .bodyEquals("It works!"); + .expectStatus().isOk() + .expectBody(String.class).value().isEqualTo("It works!"); } }