From 24358200c3b69c702d5cfed8304a3ea55ab2a6c9 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Sun, 19 Feb 2017 21:08:03 -0500 Subject: [PATCH] Assertion errors with request and response details Issue: SPR-15249 --- .../reactive/server/DefaultWebTestClient.java | 121 +++++++----- .../reactive/server/EntityExchangeResult.java | 4 +- .../web/reactive/server/ExchangeResult.java | 51 ++++- .../reactive/server/FluxExchangeResult.java | 4 +- .../web/reactive/server/HeaderAssertions.java | 58 +++--- .../web/reactive/server/StatusAssertions.java | 91 +++++---- .../server/HeaderAssertionsTests.java | 161 ++++++++++++++++ .../server/HttpHandlerConnectorTests.java | 127 +++++++++++++ .../reactive/server/StatusAssertionTests.java | 178 ++++++++++++++++++ 9 files changed, 653 insertions(+), 142 deletions(-) create mode 100644 spring-test/src/test/java/org/springframework/test/web/reactive/server/HeaderAssertionsTests.java create mode 100644 spring-test/src/test/java/org/springframework/test/web/reactive/server/HttpHandlerConnectorTests.java create mode 100644 spring-test/src/test/java/org/springframework/test/web/reactive/server/StatusAssertionTests.java 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 20341cbc86..022e8b9c0c 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 @@ -300,29 +300,31 @@ class DefaultWebTestClient implements WebTestClient { } public EntityExchangeResult consumeEmpty() { - DataBuffer buffer = this.response.body(toDataBuffers()).blockFirst(getTimeout()); - assertTrue("Expected empty body", buffer == null); + assertWithDiagnostics(() -> { + DataBuffer buffer = this.response.body(toDataBuffers()).blockFirst(getTimeout()); + assertTrue("Expected empty body", buffer == null); + }); return new EntityExchangeResult<>(this, null); } } private class DefaultResponseSpec implements ResponseSpec { - private final UndecodedExchangeResult exchangeResult; + private final UndecodedExchangeResult result; public DefaultResponseSpec(ClientHttpRequest httpRequest, ClientResponse response) { - this.exchangeResult = new UndecodedExchangeResult(httpRequest, response); + this.result = new UndecodedExchangeResult(httpRequest, response); } @Override public StatusAssertions expectStatus() { - return new StatusAssertions(this.exchangeResult, this); + return new StatusAssertions(this.result, this); } @Override public HeaderAssertions expectHeader() { - return new HeaderAssertions(this.exchangeResult, this); + return new HeaderAssertions(this.result, this); } @Override @@ -332,43 +334,44 @@ class DefaultWebTestClient implements WebTestClient { @Override public TypeBodySpec expectBody(ResolvableType elementType) { - return new DefaultTypeBodySpec(this.exchangeResult, elementType); + return new DefaultTypeBodySpec(this.result, elementType); } @Override public BodySpec expectBody() { - return new DefaultBodySpec(this.exchangeResult); + return new DefaultBodySpec(this.result); } @Override public ResponseSpec consumeWith(Consumer consumer) { - consumer.accept(this.exchangeResult); - return this; + return this.result.assertWithDiagnosticsAndReturn(() -> { + consumer.accept(this.result); + return this; + }); } @Override public ExchangeResult returnResult() { - return this.exchangeResult; + return this.result; } } private class DefaultTypeBodySpec implements TypeBodySpec { - private final UndecodedExchangeResult exchangeResult; + private final UndecodedExchangeResult result; private final ResolvableType elementType; public DefaultTypeBodySpec(UndecodedExchangeResult result, ResolvableType elementType) { - this.exchangeResult = result; + this.result = result; this.elementType = elementType; } @Override public SingleValueBodySpec value() { - EntityExchangeResult completed = this.exchangeResult.consumeSingle(this.elementType); - return new DefaultSingleValueBodySpec(completed); + return new DefaultSingleValueBodySpec(this.result.consumeSingle(this.elementType)); } @Override @@ -378,52 +381,55 @@ class DefaultWebTestClient implements WebTestClient { @Override public ListBodySpec list(int count) { - EntityExchangeResult> completed = this.exchangeResult.consumeList(this.elementType, count); - return new DefaultListBodySpec(completed); + return new DefaultListBodySpec(this.result.consumeList(this.elementType, count)); } @Override public FluxExchangeResult returnResult() { - return this.exchangeResult.decodeBody(this.elementType); + return this.result.decodeBody(this.elementType); } } private class DefaultSingleValueBodySpec implements SingleValueBodySpec { - private final EntityExchangeResult exchangeResult; + private final EntityExchangeResult result; public DefaultSingleValueBodySpec(EntityExchangeResult result) { - this.exchangeResult = result; + this.result = result; } @Override public EntityExchangeResult isEqualTo(T expected) { - assertEquals("Response body", expected, this.exchangeResult.getResponseBody()); - return returnResult(); + return this.result.assertWithDiagnosticsAndReturn(() -> { + assertEquals("Response body", expected, this.result.getResponseBody()); + return returnResult(); + }); } @Override public EntityExchangeResult returnResult() { - return new EntityExchangeResult<>(this.exchangeResult, (T) this.exchangeResult.getResponseBody()); + return new EntityExchangeResult<>(this.result, (T) this.result.getResponseBody()); } } private class DefaultListBodySpec implements ListBodySpec { - private final EntityExchangeResult> exchangeResult; + private final EntityExchangeResult> result; public DefaultListBodySpec(EntityExchangeResult> result) { - this.exchangeResult = result; + this.result = result; } @Override public EntityExchangeResult> isEqualTo(List expected) { - assertEquals("Response body", expected, this.exchangeResult.getResponseBody()); - return returnResult(); + return this.result.assertWithDiagnosticsAndReturn(() -> { + assertEquals("Response body", expected, this.result.getResponseBody()); + return returnResult(); + }); } @Override @@ -433,24 +439,28 @@ class DefaultWebTestClient implements WebTestClient { @Override public ListBodySpec contains(Object... elements) { - List elementList = Arrays.asList(elements); - String message = "Response body does not contain " + elementList; - assertTrue(message, this.exchangeResult.getResponseBody().containsAll(elementList)); + this.result.assertWithDiagnostics(() -> { + List elementList = Arrays.asList(elements); + String message = "Response body does not contain " + elementList; + assertTrue(message, this.result.getResponseBody().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.exchangeResult.getResponseBody().containsAll(Arrays.asList(elements))); + this.result.assertWithDiagnostics(() -> { + List elementList = Arrays.asList(elements); + String message = "Response body should have contained " + elementList; + assertTrue(message, !this.result.getResponseBody().containsAll(Arrays.asList(elements))); + }); return this; } @Override @SuppressWarnings("unchecked") public EntityExchangeResult> returnResult() { - return new EntityExchangeResult<>(this.exchangeResult, (List) this.exchangeResult.getResponseBody()); + return new EntityExchangeResult<>(this.result, (List) this.result.getResponseBody()); } } @@ -476,61 +486,70 @@ class DefaultWebTestClient implements WebTestClient { @Override public MapBodySpec map(ResolvableType keyType, ResolvableType valueType) { - EntityExchangeResult> completed = this.exchangeResult.consumeMap(keyType, valueType); - return new DefaultMapBodySpec(completed); + return new DefaultMapBodySpec(this.exchangeResult.consumeMap(keyType, valueType)); } } private class DefaultMapBodySpec implements MapBodySpec { - private final EntityExchangeResult> exchangeResult; + private final EntityExchangeResult> result; public DefaultMapBodySpec(EntityExchangeResult> result) { - this.exchangeResult = result; + this.result = result; } private Map getBody() { - return this.exchangeResult.getResponseBody(); + return this.result.getResponseBody(); } @Override public EntityExchangeResult> isEqualTo(Map expected) { - assertEquals("Response body map", expected, getBody()); - return returnResult(); + return this.result.assertWithDiagnosticsAndReturn(() -> { + assertEquals("Response body map", expected, getBody()); + return returnResult(); + }); } @Override public MapBodySpec hasSize(int size) { - assertEquals("Response body map size", size, getBody().size()); - return this; + return this.result.assertWithDiagnosticsAndReturn(() -> { + assertEquals("Response body map size", size, getBody().size()); + return this; + }); } @Override public MapBodySpec contains(Object key, Object value) { - assertEquals("Response body map value for key " + key, value, getBody().get(key)); - return this; + return this.result.assertWithDiagnosticsAndReturn(() -> { + assertEquals("Response body map value for key " + key, value, getBody().get(key)); + return this; + }); } @Override public MapBodySpec containsKeys(Object... keys) { - List missing = Arrays.stream(keys).filter(k -> !getBody().containsKey(k)).collect(toList()); - assertTrue("Response body map does not contain keys " + missing, missing.isEmpty()); - return this; + return this.result.assertWithDiagnosticsAndReturn(() -> { + List missing = Arrays.stream(keys).filter(k -> !getBody().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 -> !getBody().containsValue(v)).collect(toList()); - assertTrue("Response body map does not contain values " + missing, missing.isEmpty()); + this.result.assertWithDiagnostics(() -> { + List missing = Arrays.stream(values).filter(v -> !getBody().containsValue(v)).collect(toList()); + assertTrue("Response body map does not contain values " + missing, missing.isEmpty()); + }); return this; } @Override @SuppressWarnings("unchecked") public EntityExchangeResult> returnResult() { - return new EntityExchangeResult<>(this.exchangeResult, (Map) getBody()); + return new EntityExchangeResult<>(this.result, (Map) getBody()); } } diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/EntityExchangeResult.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/EntityExchangeResult.java index 32a619f8b2..df7d9db7b8 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/EntityExchangeResult.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/EntityExchangeResult.java @@ -29,8 +29,8 @@ public class EntityExchangeResult extends ExchangeResult { private final T body; - EntityExchangeResult(ExchangeResult exchange, T body) { - super(exchange); + EntityExchangeResult(ExchangeResult result, T body) { + super(result); this.body = 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 e5f5308e57..22134d52cb 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 @@ -16,6 +16,7 @@ package org.springframework.test.web.reactive.server; import java.net.URI; +import java.util.function.Supplier; import java.util.stream.Collectors; import org.springframework.http.HttpHeaders; @@ -59,12 +60,12 @@ public class ExchangeResult { this.responseHeaders = response.headers().asHttpHeaders(); } - ExchangeResult(ExchangeResult exchange) { - this.method = exchange.getMethod(); - this.url = exchange.getUrl(); - this.requestHeaders = exchange.getRequestHeaders(); - this.status = exchange.getStatus(); - this.responseHeaders = exchange.getResponseHeaders(); + ExchangeResult(ExchangeResult result) { + this.method = result.getMethod(); + this.url = result.getUrl(); + this.requestHeaders = result.getRequestHeaders(); + this.status = result.getStatus(); + this.responseHeaders = result.getResponseHeaders(); } @@ -104,18 +105,52 @@ public class ExchangeResult { } + /** + * Execute the given Runnable in the context of "this" instance and decorate + * any {@link AssertionError}s raised with request and response details. + */ + public void assertWithDiagnostics(Runnable assertion) { + try { + assertion.run(); + } + catch (AssertionError ex) { + throw new AssertionError("Assertion failed on the following exchange:" + this, ex); + } + } + + /** + * Variant of {@link #assertWithDiagnostics(Runnable)} that passes through + * a return value from the assertion code. + */ + public T assertWithDiagnosticsAndReturn(Supplier assertion) { + try { + return assertion.get(); + } + catch (AssertionError ex) { + throw new AssertionError("Assertion failed on the following exchange:" + this, ex); + } + } + + @Override public String toString() { - HttpStatus status = this.status; return "\n\n" + formatValue("Request", this.method + " " + getUrl()) + - formatValue("Status", status + " " + status.getReasonPhrase()) + + formatValue("Status", this.status + " " + getStatusReason()) + formatHeading("Response Headers") + formatHeaders(this.responseHeaders) + formatHeading("Request Headers") + formatHeaders(this.requestHeaders) + "\n" + formatValue("Response Body", formatResponseBody()); } + private String getStatusReason() { + String reason = ""; + if (this.status != null && this.status.getReasonPhrase() != null) { + reason = this.status.getReasonPhrase(); + } + return reason; + } + private String formatHeading(String heading) { return "\n" + String.format("%s", heading) + "\n"; } diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/FluxExchangeResult.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/FluxExchangeResult.java index c90a4d2c3b..b6b0224010 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/FluxExchangeResult.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/FluxExchangeResult.java @@ -34,8 +34,8 @@ public class FluxExchangeResult extends ExchangeResult { private final ResolvableType elementType; - FluxExchangeResult(ExchangeResult exchange, Flux body, ResolvableType elementType) { - super(exchange); + FluxExchangeResult(ExchangeResult result, Flux body, ResolvableType elementType) { + super(result); this.body = body; this.elementType = elementType; } 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 4a79210ead..1a456d84fc 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 @@ -16,14 +16,12 @@ package org.springframework.test.web.reactive.server; import java.util.Arrays; -import java.util.List; import java.util.regex.Pattern; import org.springframework.http.CacheControl; import org.springframework.http.ContentDisposition; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; -import org.springframework.util.CollectionUtils; import static org.springframework.test.util.AssertionErrors.assertEquals; import static org.springframework.test.util.AssertionErrors.assertTrue; @@ -52,78 +50,65 @@ public class HeaderAssertions { * Expect a header with the given name to match the specified values. */ public WebTestClient.ResponseSpec valueEquals(String headerName, String... values) { - List actual = getHeaders().get(headerName); - assertEquals("Response header [" + headerName + "]", Arrays.asList(values), actual); - return this.responseSpec; + return assertHeader(headerName, Arrays.asList(values), getHeaders().get(headerName)); } /** * Expect a header with the given name whose first value matches the * provided regex pattern. - * @param headerName the header name + * @param name the header name * @param pattern String pattern to pass to {@link Pattern#compile(String)} */ - public WebTestClient.ResponseSpec valueMatches(String headerName, String pattern) { - 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; - assertTrue(message, match); - return this.responseSpec; + public WebTestClient.ResponseSpec valueMatches(String name, String pattern) { + return this.exchangeResult.assertWithDiagnosticsAndReturn(() -> { + String value = getHeaders().getFirst(name); + assertTrue(getMessage(name) + " not found", value != null); + boolean match = Pattern.compile(pattern).matcher(value).matches(); + assertTrue(getMessage(name) + "=\'" + value + "\' does not match \'" + pattern + "\'", match); + return this.responseSpec; + }); } /** * 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; + return assertHeader("Cache-Control", cacheControl.getHeaderValue(), getHeaders().getCacheControl()); } /** * 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; + return assertHeader("Content-Disposition", contentDisposition, getHeaders().getContentDisposition()); } /** * 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; + return assertHeader("Content-Length", contentLength, getHeaders().getContentLength()); } /** * 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; + return assertHeader("Content-Type", mediaType, getHeaders().getContentType()); } /** * 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; + return assertHeader("Expires", expires, getHeaders().getExpires()); } /** * 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; + return assertHeader("Last-Modified", lastModified, getHeaders().getLastModified()); } @@ -133,4 +118,15 @@ public class HeaderAssertions { return this.exchangeResult.getResponseHeaders(); } + private String getMessage(String headerName) { + return "Response header [" + headerName + "]"; + } + + private WebTestClient.ResponseSpec assertHeader(String name, Object expected, Object actual) { + return this.exchangeResult.assertWithDiagnosticsAndReturn(() -> { + assertEquals(getMessage(name), expected, actual); + return this.responseSpec; + }); + } + } 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 e53229db00..c8748fac42 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 @@ -44,172 +44,167 @@ public class StatusAssertions { * Assert the response status as an {@link HttpStatus}. */ public WebTestClient.ResponseSpec isEqualTo(HttpStatus status) { - assertEquals("Response status", status, getStatus()); - return this.responseSpec; + return isEqualTo(status.value()); } /** * Assert the response status as an integer. */ public WebTestClient.ResponseSpec isEqualTo(int status) { - assertEquals("Response status", status, getStatus().value()); - return this.responseSpec; + return this.exchangeResult.assertWithDiagnosticsAndReturn(() -> { + assertEquals("Status", status, this.exchangeResult.getStatus().value()); + return this.responseSpec; + }); } /** * Assert the response status code is {@code HttpStatus.OK} (200). */ public WebTestClient.ResponseSpec isOk() { - assertEquals("Status", HttpStatus.OK, getStatus()); - return this.responseSpec; + return assertStatusAndReturn(HttpStatus.OK); } /** * Assert the response status code is {@code HttpStatus.CREATED} (201). */ public WebTestClient.ResponseSpec isCreated() { - assertEquals("Status", HttpStatus.CREATED, getStatus()); - return this.responseSpec; + HttpStatus expected = HttpStatus.CREATED; + return assertStatusAndReturn(expected); } /** * Assert the response status code is {@code HttpStatus.ACCEPTED} (202). */ public WebTestClient.ResponseSpec isAccepted() { - assertEquals("Status", HttpStatus.ACCEPTED, getStatus()); - return this.responseSpec; + return assertStatusAndReturn(HttpStatus.ACCEPTED); } /** * Assert the response status code is {@code HttpStatus.NO_CONTENT} (204). */ public WebTestClient.ResponseSpec isNoContent() { - assertEquals("Status", HttpStatus.NO_CONTENT, getStatus()); - return this.responseSpec; + return assertStatusAndReturn(HttpStatus.NO_CONTENT); } /** * Assert the response status code is {@code HttpStatus.FOUND} (302). */ public WebTestClient.ResponseSpec isFound() { - assertEquals("Status", HttpStatus.FOUND, getStatus()); - return this.responseSpec; + return assertStatusAndReturn(HttpStatus.FOUND); } /** * Assert the response status code is {@code HttpStatus.SEE_OTHER} (303). */ public WebTestClient.ResponseSpec isSeeOther() { - assertEquals("Status", HttpStatus.SEE_OTHER, getStatus()); - return this.responseSpec; + return assertStatusAndReturn(HttpStatus.SEE_OTHER); } /** * Assert the response status code is {@code HttpStatus.NOT_MODIFIED} (304). */ public WebTestClient.ResponseSpec isNotModified() { - assertEquals("Status", HttpStatus.NOT_MODIFIED, getStatus()); - return this.responseSpec; + return assertStatusAndReturn(HttpStatus.NOT_MODIFIED); } /** * Assert the response status code is {@code HttpStatus.TEMPORARY_REDIRECT} (307). */ public WebTestClient.ResponseSpec isTemporaryRedirect() { - assertEquals("Status", HttpStatus.TEMPORARY_REDIRECT, getStatus()); - return this.responseSpec; + return assertStatusAndReturn(HttpStatus.TEMPORARY_REDIRECT); } /** * Assert the response status code is {@code HttpStatus.PERMANENT_REDIRECT} (308). */ public WebTestClient.ResponseSpec isPermanentRedirect() { - assertEquals("Status", HttpStatus.PERMANENT_REDIRECT, getStatus()); - return this.responseSpec; + return assertStatusAndReturn(HttpStatus.PERMANENT_REDIRECT); } /** * Assert the response status code is {@code HttpStatus.BAD_REQUEST} (400). */ public WebTestClient.ResponseSpec isBadRequest() { - assertEquals("Status", HttpStatus.BAD_REQUEST, getStatus()); - return this.responseSpec; + return assertStatusAndReturn(HttpStatus.BAD_REQUEST); } /** * Assert the response status code is {@code HttpStatus.UNAUTHORIZED} (401). */ public WebTestClient.ResponseSpec isUnauthorized() { - assertEquals("Status", HttpStatus.UNAUTHORIZED, getStatus()); - return this.responseSpec; + return assertStatusAndReturn(HttpStatus.UNAUTHORIZED); } /** * Assert the response status code is {@code HttpStatus.NOT_FOUND} (404). */ public WebTestClient.ResponseSpec isNotFound() { - assertEquals("Status", HttpStatus.NOT_FOUND, getStatus()); - return this.responseSpec; + return assertStatusAndReturn(HttpStatus.NOT_FOUND); } /** * Assert the response error message. */ public WebTestClient.ResponseSpec reasonEquals(String reason) { - assertEquals("Response status reason", reason, getStatus().getReasonPhrase()); - return this.responseSpec; + return this.exchangeResult.assertWithDiagnosticsAndReturn(() -> { + HttpStatus status = this.exchangeResult.getStatus(); + assertEquals("Response status reason", reason, status.getReasonPhrase()); + return this.responseSpec; + }); } /** * Assert the response status code is in the 1xx range. */ public WebTestClient.ResponseSpec is1xxInformational() { - String message = "Range for response status value " + getStatus(); - assertEquals(message, HttpStatus.Series.INFORMATIONAL, getStatus().series()); - return this.responseSpec; + return assertSeriesAndReturn(HttpStatus.Series.INFORMATIONAL); } /** * Assert the response status code is in the 2xx range. */ public WebTestClient.ResponseSpec is2xxSuccessful() { - String message = "Range for response status value " + getStatus(); - assertEquals(message, HttpStatus.Series.SUCCESSFUL, getStatus().series()); - return this.responseSpec; + return assertSeriesAndReturn(HttpStatus.Series.SUCCESSFUL); } /** * Assert the response status code is in the 3xx range. */ public WebTestClient.ResponseSpec is3xxRedirection() { - String message = "Range for response status value " + getStatus(); - assertEquals(message, HttpStatus.Series.REDIRECTION, getStatus().series()); - return this.responseSpec; + return assertSeriesAndReturn(HttpStatus.Series.REDIRECTION); } /** * Assert the response status code is in the 4xx range. */ public WebTestClient.ResponseSpec is4xxClientError() { - String message = "Range for response status value " + getStatus(); - assertEquals(message, HttpStatus.Series.CLIENT_ERROR, getStatus().series()); - return this.responseSpec; + return assertSeriesAndReturn(HttpStatus.Series.CLIENT_ERROR); } /** * Assert the response status code is in the 5xx range. */ public WebTestClient.ResponseSpec is5xxServerError() { - String message = "Range for response status value " + getStatus(); - assertEquals(message, HttpStatus.Series.SERVER_ERROR, getStatus().series()); - return this.responseSpec; + HttpStatus.Series expected = HttpStatus.Series.SERVER_ERROR; + return assertSeriesAndReturn(expected); } - // Private methods - private HttpStatus getStatus() { - return this.exchangeResult.getStatus(); + private WebTestClient.ResponseSpec assertStatusAndReturn(HttpStatus expected) { + return this.exchangeResult.assertWithDiagnosticsAndReturn(() -> { + assertEquals("Status", expected, this.exchangeResult.getStatus()); + return this.responseSpec; + }); + } + + private WebTestClient.ResponseSpec assertSeriesAndReturn(HttpStatus.Series expected) { + return this.exchangeResult.assertWithDiagnosticsAndReturn(() -> { + HttpStatus status = this.exchangeResult.getStatus(); + String message = "Range for response status value " + status; + assertEquals(message, expected, status.series()); + return this.responseSpec; + }); } } diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/HeaderAssertionsTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/HeaderAssertionsTests.java new file mode 100644 index 0000000000..70e9382ce2 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/HeaderAssertionsTests.java @@ -0,0 +1,161 @@ +/* + * 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.concurrent.TimeUnit; + +import org.junit.Test; + +import org.springframework.http.CacheControl; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.mock.http.client.reactive.MockClientHttpRequest; +import org.springframework.web.reactive.function.client.ClientResponse; + +import static junit.framework.TestCase.assertNotNull; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Unit tests for {@link HeaderAssertions}. + * @author Rossen Stoyanchev + */ +public class HeaderAssertionsTests { + + + @Test + public void valueEquals() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.add("foo", "bar"); + HeaderAssertions assertions = headerAssertions(headers); + + // Success + assertions.valueEquals("foo", "bar"); + + try { + assertions.valueEquals("what?!", "bar"); + fail("Missing header expected"); + } + catch (AssertionError error) { + // expected + } + + try { + assertions.valueEquals("foo", "what?!"); + fail("Wrong value expected"); + } + catch (AssertionError error) { + // expected + } + + try { + assertions.valueEquals("foo", "bar", "what?!"); + fail("Wrong # of values expected"); + } + catch (AssertionError error) { + // expected + } + } + + @Test + public void valueEqualsWithMultipeValues() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.add("foo", "bar"); + headers.add("foo", "baz"); + HeaderAssertions assertions = headerAssertions(headers); + + // Success + assertions.valueEquals("foo", "bar", "baz"); + + try { + assertions.valueEquals("foo", "bar", "what?!"); + fail("Wrong value expected"); + } + catch (AssertionError error) { + // expected + } + + try { + assertions.valueEquals("foo", "bar"); + fail("Too few values expected"); + } + catch (AssertionError error) { + // expected + } + + } + + @Test + public void valueMatches() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON_UTF8); + HeaderAssertions assertions = headerAssertions(headers); + + // Success + assertions.valueMatches("Content-Type", ".*UTF-8.*"); + + try { + assertions.valueMatches("Content-Type", ".*ISO-8859-1.*"); + fail("Wrong pattern expected"); + } + catch (AssertionError error) { + Throwable cause = error.getCause(); + assertNotNull(cause); + assertEquals("Response header [Content-Type]='application/json;charset=UTF-8' " + + "does not match '.*ISO-8859-1.*'", cause.getMessage()); + } + } + + @Test + public void cacheControl() throws Exception { + + CacheControl control = CacheControl.maxAge(1, TimeUnit.HOURS).noTransform(); + + HttpHeaders headers = new HttpHeaders(); + headers.setCacheControl(control.getHeaderValue()); + HeaderAssertions assertions = headerAssertions(headers); + + // Success + assertions.cacheControl(control); + + try { + assertions.cacheControl(CacheControl.noStore()); + fail("Wrong value expected"); + } + catch (AssertionError error) { + // Expected + } + } + + private HeaderAssertions headerAssertions(HttpHeaders responseHeaders) { + + ClientResponse.Headers headers = mock(ClientResponse.Headers.class); + when(headers.asHttpHeaders()).thenReturn(responseHeaders); + + ClientResponse response = mock(ClientResponse.class); + when(response.headers()).thenReturn(headers); + + MockClientHttpRequest request = new MockClientHttpRequest(HttpMethod.GET, URI.create("/")); + ExchangeResult result = new ExchangeResult(request, response); + + return new HeaderAssertions(result, mock(WebTestClient.ResponseSpec.class)); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/HttpHandlerConnectorTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/HttpHandlerConnectorTests.java new file mode 100644 index 0000000000..3649227c64 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/HttpHandlerConnectorTests.java @@ -0,0 +1,127 @@ +/* + * 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.time.Duration; +import java.util.Arrays; +import java.util.function.Function; + +import org.junit.Test; +import reactor.core.publisher.Mono; + +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DefaultDataBufferFactory; +import org.springframework.core.io.buffer.support.DataBufferTestUtils; +import org.springframework.http.HttpCookie; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ReactiveHttpOutputMessage; +import org.springframework.http.ResponseCookie; +import org.springframework.http.client.reactive.ClientHttpResponse; +import org.springframework.http.server.reactive.HttpHandler; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertEquals; + +/** + * Unit tests for {@link HttpHandlerConnector}. + * @author Rossen Stoyanchev + */ +public class HttpHandlerConnectorTests { + + + @Test + public void adaptRequest() throws Exception { + + TestHttpHandler handler = new TestHttpHandler(response -> { + response.setStatusCode(HttpStatus.OK); + return response.setComplete(); + }); + + new HttpHandlerConnector(handler).connect(HttpMethod.POST, URI.create("/custom-path"), + request -> { + request.getHeaders().put("custom-header", Arrays.asList("h0", "h1")); + request.getCookies().add("custom-cookie", new HttpCookie("custom-cookie", "c0")); + return request.writeWith(Mono.just(toDataBuffer("Custom body"))); + }).block(Duration.ofSeconds(5)); + + MockServerHttpRequest request = (MockServerHttpRequest) handler.getSavedRequest(); + assertEquals(HttpMethod.POST, request.getMethod()); + assertEquals("/custom-path", request.getURI().toString()); + + assertEquals(Arrays.asList("h0", "h1"), request.getHeaders().get("custom-header")); + assertEquals(new HttpCookie("custom-cookie", "c0"), request.getCookies().getFirst("custom-cookie")); + + DataBuffer buffer = request.getBody().blockFirst(Duration.ZERO); + assertEquals("Custom body", DataBufferTestUtils.dumpString(buffer, UTF_8)); + } + + @Test + public void adaptResponse() throws Exception { + + ResponseCookie cookie = ResponseCookie.from("custom-cookie", "c0").build(); + + TestHttpHandler handler = new TestHttpHandler(response -> { + response.setStatusCode(HttpStatus.OK); + response.getHeaders().put("custom-header", Arrays.asList("h0", "h1")); + response.getCookies().add(cookie.getName(), cookie); + return response.writeWith(Mono.just(toDataBuffer("Custom body"))); + }); + + ClientHttpResponse response = new HttpHandlerConnector(handler) + .connect(HttpMethod.GET, URI.create("/custom-path"), ReactiveHttpOutputMessage::setComplete) + .block(Duration.ofSeconds(5)); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(Arrays.asList("h0", "h1"), response.getHeaders().get("custom-header")); + assertEquals(cookie, response.getCookies().getFirst("custom-cookie")); + + DataBuffer buffer = response.getBody().blockFirst(Duration.ZERO); + assertEquals("Custom body", DataBufferTestUtils.dumpString(buffer, UTF_8)); + } + + private DataBuffer toDataBuffer(String body) { + return new DefaultDataBufferFactory().wrap(body.getBytes(UTF_8)); + } + + + private static class TestHttpHandler implements HttpHandler { + + private ServerHttpRequest savedRequest; + + private final Function> responseMonoFunction; + + + public TestHttpHandler(Function> function) { + this.responseMonoFunction = function; + } + + public ServerHttpRequest getSavedRequest() { + return this.savedRequest; + } + + @Override + public Mono handle(ServerHttpRequest request, ServerHttpResponse response) { + this.savedRequest = request; + return this.responseMonoFunction.apply(response); + } + } + +} 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 new file mode 100644 index 0000000000..beabc3a7b8 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/StatusAssertionTests.java @@ -0,0 +1,178 @@ +/* + * 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 org.junit.Test; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.mock.http.client.reactive.MockClientHttpRequest; +import org.springframework.web.reactive.function.client.ClientResponse; + +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Unit tests for {@link StatusAssertions}. + * @author Rossen Stoyanchev + */ +public class StatusAssertionTests { + + + @Test + public void isEqualTo() throws Exception { + + StatusAssertions assertions = statusAssertions(HttpStatus.CONFLICT); + + // Success + assertions.isEqualTo(HttpStatus.CONFLICT); + assertions.isEqualTo(409); + + try { + assertions.isEqualTo(HttpStatus.REQUEST_TIMEOUT); + fail("Wrong status expected"); + } + catch (AssertionError error) { + // Expected + } + + try { + assertions.isEqualTo(408); + fail("Wrong status value expected"); + } + catch (AssertionError error) { + // Expected + } + } + + @Test + public void reasonEquals() throws Exception { + + StatusAssertions assertions = statusAssertions(HttpStatus.CONFLICT); + + // Success + assertions.reasonEquals("Conflict"); + + try { + assertions.reasonEquals("Request Timeout"); + fail("Wrong reason expected"); + } + catch (AssertionError error) { + // Expected + } + } + + @Test + public void statusSerius1xx() throws Exception { + StatusAssertions assertions = statusAssertions(HttpStatus.CONTINUE); + + // Success + assertions.is1xxInformational(); + + try { + assertions.is2xxSuccessful(); + fail("Wrong series expected"); + } + catch (AssertionError error) { + // Expected + } + } + + @Test + public void statusSerius2xx() throws Exception { + StatusAssertions assertions = statusAssertions(HttpStatus.OK); + + // Success + assertions.is2xxSuccessful(); + + try { + assertions.is5xxServerError(); + fail("Wrong series expected"); + } + catch (AssertionError error) { + // Expected + } + } + + @Test + public void statusSerius3xx() throws Exception { + StatusAssertions assertions = statusAssertions(HttpStatus.PERMANENT_REDIRECT); + + // Success + assertions.is3xxRedirection(); + + try { + assertions.is2xxSuccessful(); + fail("Wrong series expected"); + } + catch (AssertionError error) { + // Expected + } + } + + @Test + public void statusSerius4xx() throws Exception { + StatusAssertions assertions = statusAssertions(HttpStatus.BAD_REQUEST); + + // Success + assertions.is4xxClientError(); + + try { + assertions.is2xxSuccessful(); + fail("Wrong series expected"); + } + catch (AssertionError error) { + // Expected + } + } + + @Test + public void statusSerius5xx() throws Exception { + StatusAssertions assertions = statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR); + + // Success + assertions.is5xxServerError(); + + try { + assertions.is2xxSuccessful(); + fail("Wrong series expected"); + } + catch (AssertionError error) { + // Expected + } + } + + + private StatusAssertions statusAssertions(HttpStatus status) { + + ClientResponse.Headers headers = mock(ClientResponse.Headers.class); + when(headers.asHttpHeaders()).thenReturn(new HttpHeaders()); + + ClientResponse response = mock(ClientResponse.class); + when(response.statusCode()).thenReturn(status); + when(response.headers()).thenReturn(headers); + + MockClientHttpRequest request = new MockClientHttpRequest(HttpMethod.GET, URI.create("/")); + ExchangeResult result = new ExchangeResult(request, response); + + return new StatusAssertions(result, mock(WebTestClient.ResponseSpec.class)); + } + +}