Assertion errors with request and response details

Issue: SPR-15249
This commit is contained in:
Rossen Stoyanchev
2017-02-19 21:08:03 -05:00
parent d1a64e1122
commit 24358200c3
9 changed files with 653 additions and 142 deletions

View File

@@ -300,29 +300,31 @@ class DefaultWebTestClient implements WebTestClient {
}
public EntityExchangeResult<Void> 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<ExchangeResult> 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<List<?>> completed = this.exchangeResult.consumeList(this.elementType, count);
return new DefaultListBodySpec(completed);
return new DefaultListBodySpec(this.result.consumeList(this.elementType, count));
}
@Override
public <T> FluxExchangeResult<T> 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 <T> EntityExchangeResult<T> 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 <T> EntityExchangeResult<T> 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<List<?>> exchangeResult;
private final EntityExchangeResult<List<?>> result;
public DefaultListBodySpec(EntityExchangeResult<List<?>> result) {
this.exchangeResult = result;
this.result = result;
}
@Override
public <T> EntityExchangeResult<List<T>> isEqualTo(List<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
@@ -433,24 +439,28 @@ class DefaultWebTestClient implements WebTestClient {
@Override
public ListBodySpec contains(Object... elements) {
List<Object> elementList = Arrays.asList(elements);
String message = "Response body does not contain " + elementList;
assertTrue(message, this.exchangeResult.getResponseBody().containsAll(elementList));
this.result.assertWithDiagnostics(() -> {
List<Object> 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<Object> 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<Object> 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 <T> EntityExchangeResult<List<T>> returnResult() {
return new EntityExchangeResult<>(this.exchangeResult, (List<T>) this.exchangeResult.getResponseBody());
return new EntityExchangeResult<>(this.result, (List<T>) this.result.getResponseBody());
}
}
@@ -476,61 +486,70 @@ class DefaultWebTestClient implements WebTestClient {
@Override
public MapBodySpec map(ResolvableType keyType, ResolvableType valueType) {
EntityExchangeResult<Map<?, ?>> 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<Map<?, ?>> exchangeResult;
private final EntityExchangeResult<Map<?, ?>> result;
public DefaultMapBodySpec(EntityExchangeResult<Map<?, ?>> result) {
this.exchangeResult = result;
this.result = result;
}
private Map<?, ?> getBody() {
return this.exchangeResult.getResponseBody();
return this.result.getResponseBody();
}
@Override
public <K, V> EntityExchangeResult<Map<K, V>> isEqualTo(Map<K, V> 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 <K, V> EntityExchangeResult<Map<K, V>> returnResult() {
return new EntityExchangeResult<>(this.exchangeResult, (Map<K, V>) getBody());
return new EntityExchangeResult<>(this.result, (Map<K, V>) getBody());
}
}

View File

@@ -29,8 +29,8 @@ public class EntityExchangeResult<T> extends ExchangeResult {
private final T body;
EntityExchangeResult(ExchangeResult exchange, T body) {
super(exchange);
EntityExchangeResult(ExchangeResult result, T body) {
super(result);
this.body = body;
}

View File

@@ -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> T assertWithDiagnosticsAndReturn(Supplier<T> 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";
}

View File

@@ -34,8 +34,8 @@ public class FluxExchangeResult<T> extends ExchangeResult {
private final ResolvableType elementType;
FluxExchangeResult(ExchangeResult exchange, Flux<T> body, ResolvableType elementType) {
super(exchange);
FluxExchangeResult(ExchangeResult result, Flux<T> body, ResolvableType elementType) {
super(result);
this.body = body;
this.elementType = elementType;
}

View File

@@ -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<String> 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<String> 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;
});
}
}

View File

@@ -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;
});
}
}