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