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 77e514dd757..ff1c20d5020 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -72,6 +72,9 @@ public class ExchangeResult { @Nullable private final String uriTemplate; + @Nullable + final Object mockServerResult; + /** * Create an instance with an HTTP request and response along with promises @@ -83,9 +86,11 @@ public class ExchangeResult { * @param responseBody capture of serialized response body content * @param timeout how long to wait for content to materialize * @param uriTemplate the URI template used to set up the request, if any + * @param serverResult the result of a mock server exchange if applicable. */ ExchangeResult(ClientHttpRequest request, ClientHttpResponse response, - Mono requestBody, Mono responseBody, Duration timeout, @Nullable String uriTemplate) { + Mono requestBody, Mono responseBody, Duration timeout, @Nullable String uriTemplate, + @Nullable Object serverResult) { Assert.notNull(request, "ClientHttpRequest is required"); Assert.notNull(response, "ClientHttpResponse is required"); @@ -98,6 +103,7 @@ public class ExchangeResult { this.responseBody = responseBody; this.timeout = timeout; this.uriTemplate = uriTemplate; + this.mockServerResult = serverResult; } /** @@ -110,6 +116,7 @@ public class ExchangeResult { this.responseBody = other.responseBody; this.timeout = other.timeout; this.uriTemplate = other.uriTemplate; + this.mockServerResult = other.mockServerResult; } @@ -195,6 +202,16 @@ public class ExchangeResult { return this.responseBody.block(this.timeout); } + /** + * Return the result from the mock server exchange, if applicable, for + * further assertions on the state of the server response. + * @since 5.3 + * @see org.springframework.test.web.servlet.client.MockMvcTestClient#resultActionsFor(ExchangeResult) + */ + @Nullable + public Object getMockServerResult() { + return this.mockServerResult; + } /** * Execute the given Runnable, catch any {@link AssertionError}, decorate @@ -222,7 +239,8 @@ public class ExchangeResult { "< " + getStatus() + " " + getStatus().getReasonPhrase() + "\n" + "< " + formatHeaders(getResponseHeaders(), "\n< ") + "\n" + "\n" + - formatBody(getResponseHeaders().getContentType(), this.responseBody) +"\n"; + formatBody(getResponseHeaders().getContentType(), this.responseBody) +"\n" + + formatMockServerResult(); } private String formatHeaders(HttpHeaders headers, String delimiter) { @@ -252,4 +270,10 @@ public class ExchangeResult { .block(this.timeout); } + private String formatMockServerResult() { + return (this.mockServerResult != null ? + "\n====================== MockMvc (Server) ===============================\n" + + this.mockServerResult + "\n" : ""); + } + } diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/MockServerClientHttpResponse.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/MockServerClientHttpResponse.java new file mode 100644 index 00000000000..8fe12d92659 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/MockServerClientHttpResponse.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2020 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.test.web.reactive.server; + +import org.springframework.http.client.reactive.ClientHttpResponse; + +/** + * Simple {@link ClientHttpResponse} extension that also exposes a result object + * from the underlying mock server exchange for further assertions on the state + * of the server response after the request is performed. + * + * @author Rossen Stoyanchev + * @since 5.3 + */ +public interface MockServerClientHttpResponse extends ClientHttpResponse { + + /** + * Return the result object with the server request and response. + */ + Object getServerResult(); + +} 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 fcc8fc48f97..76329b6a6b0 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 @@ -88,15 +88,16 @@ class WiretapConnector implements ClientHttpConnector { * Create the {@link ExchangeResult} for the given "request-id" header value. */ ExchangeResult getExchangeResult(String requestId, @Nullable String uriTemplate, Duration timeout) { - ClientExchangeInfo info = this.exchanges.remove(requestId); - Assert.state(info != null, () -> { + ClientExchangeInfo clientInfo = this.exchanges.remove(requestId); + Assert.state(clientInfo != null, () -> { String header = WebTestClient.WEBTESTCLIENT_REQUEST_ID; return "No match for " + header + "=" + requestId; }); - return new ExchangeResult(info.getRequest(), info.getResponse(), - info.getRequest().getRecorder().getContent(), - info.getResponse().getRecorder().getContent(), - timeout, uriTemplate); + return new ExchangeResult(clientInfo.getRequest(), clientInfo.getResponse(), + clientInfo.getRequest().getRecorder().getContent(), + clientInfo.getResponse().getRecorder().getContent(), + timeout, uriTemplate, + clientInfo.getResponse().getMockServerResult()); } @@ -279,6 +280,12 @@ class WiretapConnector implements ClientHttpConnector { public Flux getBody() { return Flux.from(this.recorder.getPublisherToUse()); } + + @Nullable + public Object getMockServerResult() { + return (getDelegate() instanceof MockServerClientHttpResponse ? + ((MockServerClientHttpResponse) getDelegate()).getServerResult() : null); + } } } diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/HeaderAssertionTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/HeaderAssertionTests.java index 8cec305eb36..dd5cebe838f 100644 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/HeaderAssertionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/HeaderAssertionTests.java @@ -244,7 +244,7 @@ class HeaderAssertionTests { MonoProcessor emptyContent = MonoProcessor.fromSink(Sinks.one()); emptyContent.onComplete(); - ExchangeResult result = new ExchangeResult(request, response, emptyContent, emptyContent, Duration.ZERO, null); + ExchangeResult result = new ExchangeResult(request, response, emptyContent, emptyContent, Duration.ZERO, null, null); return new HeaderAssertions(result, mock(WebTestClient.ResponseSpec.class)); } diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/StatusAssertionTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/StatusAssertionTests.java index 9f06a724664..c9fd9b68d06 100644 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/StatusAssertionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/StatusAssertionTests.java @@ -160,7 +160,7 @@ class StatusAssertionTests { MonoProcessor emptyContent = MonoProcessor.fromSink(Sinks.one()); emptyContent.onComplete(); - ExchangeResult result = new ExchangeResult(request, response, emptyContent, emptyContent, Duration.ZERO, null); + ExchangeResult result = new ExchangeResult(request, response, emptyContent, emptyContent, Duration.ZERO, null, null); return new StatusAssertions(result, mock(WebTestClient.ResponseSpec.class)); }