diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/CookieAssertions.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/CookieAssertions.java index dd47cec18b..e3328c8f5c 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/CookieAssertions.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/CookieAssertions.java @@ -36,12 +36,13 @@ public class CookieAssertions extends AbstractCookieAssertions getResponseCookies() { + return getExchangeResult().getResponseCookies(); } @Override - protected MultiValueMap getResponseCookies() { - return exchangeResult.getResponseCookies(); + protected void assertWithDiagnostics(Runnable assertion) { + getExchangeResult().assertWithDiagnostics(assertion); } + } 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 d8ca7dadab..c2c3a3a7fb 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 @@ -38,12 +38,13 @@ public class HeaderAssertions extends AbstractHeaderAssertions { - XpathAssertions(WebTestClient.BodyContentSpec spec, - String expression, @Nullable Map namespaces, Object... args) { + + XpathAssertions( + WebTestClient.BodyContentSpec spec, + String expression, @Nullable Map namespaces, Object... args) { + super(spec, expression, namespaces, args); } + @Override protected Optional getResponseHeaders() { - return Optional.of(bodySpec.returnResult()) - .map(ExchangeResult::getResponseHeaders); + return Optional.of(getBodySpec().returnResult()).map(ExchangeResult::getResponseHeaders); } @Override protected byte[] getContent() { - byte[] body = this.bodySpec.returnResult().getResponseBody(); + byte[] body = getBodySpec().returnResult().getResponseBody(); Assert.notNull(body, "Expected body content"); return body; } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/CookieAssertions.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/CookieAssertions.java index e5a033f825..8f9bd624ab 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/CookieAssertions.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/CookieAssertions.java @@ -35,12 +35,13 @@ public class CookieAssertions extends AbstractCookieAssertions getResponseCookies() { + return getExchangeResult().getResponseCookies(); } @Override - protected MultiValueMap getResponseCookies() { - return exchangeResult.getResponseCookies(); + protected void assertWithDiagnostics(Runnable assertion) { + getExchangeResult().assertWithDiagnostics(assertion); } + } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/EntityExchangeResult.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/EntityExchangeResult.java index f9c9702f8b..2999fffcf5 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/EntityExchangeResult.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/EntityExchangeResult.java @@ -23,6 +23,7 @@ import org.jspecify.annotations.Nullable; * extracted to a representation of type {@code }. * * @author Rob Worsnop + * @since 7.0 * @param the response body type */ public class EntityExchangeResult extends ExchangeResult { diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/HeaderAssertions.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/HeaderAssertions.java index 777097e67d..1a3dfab4ad 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/HeaderAssertions.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/HeaderAssertions.java @@ -33,13 +33,15 @@ public class HeaderAssertions extends AbstractHeaderAssertions getResponseHeaders() { - return Optional.of(bodySpec.returnResult()) - .map(ExchangeResult::getResponseHeaders); + return Optional.of(getBodySpec().returnResult()).map(ExchangeResult::getResponseHeaders); } @Override protected byte[] getContent() { - byte[] body = this.bodySpec.returnResult().getResponseBody(); + byte[] body = getBodySpec().returnResult().getResponseBody(); Assert.notNull(body, "Expected body content"); return body; } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/package-info.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/package-info.java index ed071659d1..94c3686561 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/package-info.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/package-info.java @@ -1,8 +1,7 @@ /** * Support for testing Spring MVC applications via - * {@link org.springframework.test.web.reactive.server.WebTestClient} - * with {@link org.springframework.test.web.servlet.MockMvc} for server request - * handling. + * {@link org.springframework.test.web.servlet.client.RestTestClient} with + * {@link org.springframework.test.web.servlet.MockMvc} for server request handling. */ @NullMarked diff --git a/spring-test/src/main/java/org/springframework/test/web/support/AbstractCookieAssertions.java b/spring-test/src/main/java/org/springframework/test/web/support/AbstractCookieAssertions.java index 0527572610..e564b8b911 100644 --- a/spring-test/src/main/java/org/springframework/test/web/support/AbstractCookieAssertions.java +++ b/spring-test/src/main/java/org/springframework/test/web/support/AbstractCookieAssertions.java @@ -34,19 +34,42 @@ import static org.springframework.test.util.AssertionErrors.fail; * Assertions on cookies of the response. * * @author Rob Worsnop + * @author Rossen Stoyanchev * @since 7.0 * @param the type of the exchange result * @param the type of the response spec */ public abstract class AbstractCookieAssertions { - protected final E exchangeResult; + + private final E exchangeResult; + private final R responseSpec; + protected AbstractCookieAssertions(E exchangeResult, R responseSpec) { this.exchangeResult = exchangeResult; this.responseSpec = responseSpec; } + + /** + * Return the exchange result. + */ + protected E getExchangeResult() { + return this.exchangeResult; + } + + /** + * Subclasses must implement this to provide access to response cookies. + */ + protected abstract MultiValueMap getResponseCookies(); + + /** + * Subclasses must implement this to assert with diagnostics. + */ + protected abstract void assertWithDiagnostics(Runnable assertion); + + /** * Expect a response cookie with the given name to match the specified value. */ @@ -224,10 +247,6 @@ public abstract class AbstractCookieAssertions { return this.responseSpec; } - protected abstract void assertWithDiagnostics(Runnable assertion); - - protected abstract MultiValueMap getResponseCookies(); - private ResponseCookie getCookie(String name) { ResponseCookie cookie = getResponseCookies().getFirst(name); if (cookie != null) { diff --git a/spring-test/src/main/java/org/springframework/test/web/support/AbstractHeaderAssertions.java b/spring-test/src/main/java/org/springframework/test/web/support/AbstractHeaderAssertions.java index a10aada91c..166de6e386 100644 --- a/spring-test/src/main/java/org/springframework/test/web/support/AbstractHeaderAssertions.java +++ b/spring-test/src/main/java/org/springframework/test/web/support/AbstractHeaderAssertions.java @@ -40,19 +40,42 @@ import static org.springframework.test.util.AssertionErrors.fail; * Assertions on headers of the response. * * @author Rob Worsnop + * @author Rossen Stoyanchev * @since 7.0 * @param the type of the exchange result * @param the type of the response spec */ public abstract class AbstractHeaderAssertions { - protected final E exchangeResult; + + private final E exchangeResult; + private final R responseSpec; + protected AbstractHeaderAssertions(E exchangeResult, R responseSpec) { this.exchangeResult = exchangeResult; this.responseSpec = responseSpec; } + + /** + * Return the exchange result. + */ + protected E getExchangeResult() { + return this.exchangeResult; + } + + /** + * Subclasses must implement this to provide access to response headers. + */ + protected abstract HttpHeaders getResponseHeaders(); + + /** + * Subclasses must implement this to assert with diagnostics. + */ + protected abstract void assertWithDiagnostics(Runnable assertion); + + /** * Expect a header with the given name to match the specified values. */ @@ -277,10 +300,6 @@ public abstract class AbstractHeaderAssertions { return assertHeader("Location", URI.create(location), getResponseHeaders().getLocation()); } - protected abstract void assertWithDiagnostics(Runnable assertion); - - protected abstract HttpHeaders getResponseHeaders(); - private R assertHeader(String name, @Nullable Object expected, @Nullable Object actual) { assertWithDiagnostics(() -> { String message = getMessage(name); diff --git a/spring-test/src/main/java/org/springframework/test/web/support/AbstractJsonPathAssertions.java b/spring-test/src/main/java/org/springframework/test/web/support/AbstractJsonPathAssertions.java index a34facd138..2254d39abb 100644 --- a/spring-test/src/main/java/org/springframework/test/web/support/AbstractJsonPathAssertions.java +++ b/spring-test/src/main/java/org/springframework/test/web/support/AbstractJsonPathAssertions.java @@ -26,6 +26,18 @@ import org.springframework.core.ParameterizedTypeReference; import org.springframework.test.util.JsonPathExpectationsHelper; import org.springframework.util.Assert; +/** + * Base class for applying + * JsonPath assertions + * in RestTestClient and WebTestClient. + * + * @author Rob Worsnop + * @author Rossen Stoyanchev + * @since 7.0 + * @param the type of body spec (RestTestClient vs WebTestClient specific) + * @see https://github.com/jayway/JsonPath + * @see JsonPathExpectationsHelper + */ public abstract class AbstractJsonPathAssertions { private final B bodySpec; @@ -34,6 +46,7 @@ public abstract class AbstractJsonPathAssertions { private final JsonPathExpectationsHelper pathHelper; + protected AbstractJsonPathAssertions(B spec, String content, String expression, @Nullable Configuration configuration) { Assert.hasText(expression, "expression must not be null or empty"); this.bodySpec = spec; @@ -41,6 +54,7 @@ public abstract class AbstractJsonPathAssertions { this.pathHelper = new JsonPathExpectationsHelper(expression, configuration); } + /** * Applies {@link JsonPathExpectationsHelper#assertValue(String, Object)}. */ diff --git a/spring-test/src/main/java/org/springframework/test/web/support/AbstractStatusAssertions.java b/spring-test/src/main/java/org/springframework/test/web/support/AbstractStatusAssertions.java index 719ba8cedf..9573e11920 100644 --- a/spring-test/src/main/java/org/springframework/test/web/support/AbstractStatusAssertions.java +++ b/spring-test/src/main/java/org/springframework/test/web/support/AbstractStatusAssertions.java @@ -31,18 +31,42 @@ import static org.springframework.test.util.AssertionErrors.assertNotNull; * Assertions on the response status. * * @author Rob Worsnop + * @author Rossen Stoyanchev + * @since 7.0 * @param the type of the exchange result * @param the type of the response spec */ public abstract class AbstractStatusAssertions { - protected final E exchangeResult; + + private final E exchangeResult; + private final R responseSpec; + protected AbstractStatusAssertions(E exchangeResult, R responseSpec) { this.exchangeResult = exchangeResult; this.responseSpec = responseSpec; } + + /** + * Return the exchange result. + */ + protected E getExchangeResult() { + return this.exchangeResult; + } + + /** + * Subclasses must implement this to provide access to the response status. + */ + protected abstract HttpStatusCode getStatus(); + + /** + * Subclasses must implement this to assert with diagnostics. + */ + protected abstract void assertWithDiagnostics(Runnable assertion); + + /** * Assert the response status as an {@link HttpStatusCode}. */ @@ -226,10 +250,6 @@ public abstract class AbstractStatusAssertions { return this.responseSpec; } - protected abstract void assertWithDiagnostics(Runnable assertion); - - protected abstract HttpStatusCode getStatus(); - private R assertStatusAndReturn(HttpStatus expected) { assertNotNull("exchangeResult unexpectedly null", this.exchangeResult); HttpStatusCode actual = getStatus(); diff --git a/spring-test/src/main/java/org/springframework/test/web/support/AbstractXpathAssertions.java b/spring-test/src/main/java/org/springframework/test/web/support/AbstractXpathAssertions.java index 138c2de92d..4614d7dec3 100644 --- a/spring-test/src/main/java/org/springframework/test/web/support/AbstractXpathAssertions.java +++ b/spring-test/src/main/java/org/springframework/test/web/support/AbstractXpathAssertions.java @@ -30,12 +30,24 @@ import org.springframework.http.HttpHeaders; import org.springframework.test.util.XpathExpectationsHelper; import org.springframework.util.MimeType; +/** + * Base class for applying XPath assertions in RestTestClient and WebTestClient. + * + * @author Rob Worsnop + * @author Rossen Stoyanchev + * @since 7.0 + * @param the type of body spec (RestTestClient vs WebTestClient specific) + */ public abstract class AbstractXpathAssertions { - protected final B bodySpec; + + private final B bodySpec; private final XpathExpectationsHelper xpathHelper; - public AbstractXpathAssertions(B spec, String expression, @Nullable Map namespaces, Object... args) { + + public AbstractXpathAssertions( + B spec, String expression, @Nullable Map namespaces, Object... args) { + this.bodySpec = spec; this.xpathHelper = initXpathHelper(expression, namespaces, args); } @@ -52,6 +64,24 @@ public abstract class AbstractXpathAssertions { } + /** + * Return the body spec. + */ + protected B getBodySpec() { + return this.bodySpec; + } + + /** + * Subclasses must implement this to provide access to response headers. + */ + protected abstract Optional getResponseHeaders(); + + /** + * Subclasses must implement this to provide access to the response content. + */ + protected abstract byte[] getContent(); + + /** * Delegates to {@link XpathExpectationsHelper#assertString(byte[], String, String)}. */ @@ -175,9 +205,6 @@ public abstract class AbstractXpathAssertions { return super.hashCode(); } - protected abstract Optional getResponseHeaders(); - - protected abstract byte[] getContent(); /** * Lets us be able to use lambda expressions that could throw checked exceptions, since diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/CookieAssertionsTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/CookieAssertionsTests.java deleted file mode 100644 index 41246500bb..0000000000 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/CookieAssertionsTests.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright 2002-present 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 java.net.URI; -import java.time.Duration; - -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; - -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseCookie; -import org.springframework.mock.http.client.reactive.MockClientHttpRequest; -import org.springframework.mock.http.client.reactive.MockClientHttpResponse; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.hamcrest.Matchers.equalTo; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link CookieAssertions} - * - * @author Rossen Stoyanchev - */ -public class CookieAssertionsTests { - - private final ResponseCookie cookie = ResponseCookie.from("foo", "bar") - .maxAge(Duration.ofMinutes(30)) - .domain("foo.com") - .path("/foo") - .secure(true) - .httpOnly(true) - .partitioned(true) - .sameSite("Lax") - .build(); - - private final CookieAssertions assertions = cookieAssertions(cookie); - - - @Test - void valueEquals() { - assertions.valueEquals("foo", "bar"); - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.valueEquals("what?!", "bar")); - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.valueEquals("foo", "what?!")); - } - - @Test - void value() { - assertions.value("foo", equalTo("bar")); - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.value("foo", equalTo("what?!"))); - } - - @Test - void exists() { - assertions.exists("foo"); - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.exists("what?!")); - } - - @Test - void doesNotExist() { - assertions.doesNotExist("what?!"); - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.doesNotExist("foo")); - } - - @Test - void maxAge() { - assertions.maxAge("foo", Duration.ofMinutes(30)); - assertThatExceptionOfType(AssertionError.class) - .isThrownBy(() -> assertions.maxAge("foo", Duration.ofMinutes(29))); - - assertions.maxAge("foo", equalTo(Duration.ofMinutes(30).getSeconds())); - assertThatExceptionOfType(AssertionError.class) - .isThrownBy(() -> assertions.maxAge("foo", equalTo(Duration.ofMinutes(29).getSeconds()))); - } - - @Test - void domain() { - assertions.domain("foo", "foo.com"); - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.domain("foo", "what.com")); - - assertions.domain("foo", equalTo("foo.com")); - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.domain("foo", equalTo("what.com"))); - } - - @Test - void path() { - assertions.path("foo", "/foo"); - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.path("foo", "/what")); - - assertions.path("foo", equalTo("/foo")); - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.path("foo", equalTo("/what"))); - } - - @Test - void secure() { - assertions.secure("foo", true); - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.secure("foo", false)); - } - - @Test - void httpOnly() { - assertions.httpOnly("foo", true); - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.httpOnly("foo", false)); - } - - @Test - void partitioned() { - assertions.partitioned("foo", true); - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.partitioned("foo", false)); - } - - @Test - void sameSite() { - assertions.sameSite("foo", "Lax"); - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.sameSite("foo", "Strict")); - } - - - private CookieAssertions cookieAssertions(ResponseCookie cookie) { - MockClientHttpRequest request = new MockClientHttpRequest(HttpMethod.GET, URI.create("/")); - MockClientHttpResponse response = new MockClientHttpResponse(HttpStatus.OK); - response.getCookies().add(cookie.getName(), cookie); - - ExchangeResult result = new ExchangeResult( - request, response, Mono.empty(), Mono.empty(), Duration.ZERO, null, null); - - return new CookieAssertions(result, mock()); - } - -} 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 deleted file mode 100644 index aa795f13b2..0000000000 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/HeaderAssertionTests.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright 2002-present 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 java.net.URI; -import java.time.Duration; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.concurrent.TimeUnit; - -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; - -import org.springframework.http.CacheControl; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.mock.http.client.reactive.MockClientHttpRequest; -import org.springframework.mock.http.client.reactive.MockClientHttpResponse; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.hasItems; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link HeaderAssertions}. - * - * @author Rossen Stoyanchev - * @author Sam Brannen - */ -class HeaderAssertionTests { - - @Test - void valueEquals() { - HttpHeaders headers = new HttpHeaders(); - headers.add("foo", "bar"); - HeaderAssertions assertions = headerAssertions(headers); - - // Success - assertions.valueEquals("foo", "bar"); - - // Missing header - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.valueEquals("what?!", "bar")); - - // Wrong value - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.valueEquals("foo", "what?!")); - - // Wrong # of values - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.valueEquals("foo", "bar", "what?!")); - } - - @Test - void valueEqualsWithMultipleValues() { - HttpHeaders headers = new HttpHeaders(); - headers.add("foo", "bar"); - headers.add("foo", "baz"); - HeaderAssertions assertions = headerAssertions(headers); - - // Success - assertions.valueEquals("foo", "bar", "baz"); - - // Wrong value - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.valueEquals("foo", "bar", "what?!")); - - // Too few values - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.valueEquals("foo", "bar")); - } - - @Test - void valueMatches() { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.parseMediaType("application/json;charset=UTF-8")); - HeaderAssertions assertions = headerAssertions(headers); - - // Success - assertions.valueMatches("Content-Type", ".*UTF-8.*"); - - // Wrong pattern - assertThatExceptionOfType(AssertionError.class) - .isThrownBy(() -> assertions.valueMatches("Content-Type", ".*ISO-8859-1.*")) - .satisfies(ex -> assertThat(ex).hasMessage("Response header " + - "'Content-Type'=[application/json;charset=UTF-8] does not match " + - "[.*ISO-8859-1.*]")); - } - - @Test - void valueMatchesWithNonexistentHeader() { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.parseMediaType("application/json;charset=UTF-8")); - HeaderAssertions assertions = headerAssertions(headers); - - assertThatExceptionOfType(AssertionError.class) - .isThrownBy(() -> assertions.valueMatches("Content-XYZ", ".*ISO-8859-1.*")) - .withMessage("Response header 'Content-XYZ' not found"); - } - - @Test - void valuesMatch() { - HttpHeaders headers = new HttpHeaders(); - headers.add("foo", "value1"); - headers.add("foo", "value2"); - headers.add("foo", "value3"); - HeaderAssertions assertions = headerAssertions(headers); - - assertions.valuesMatch("foo", "val.*1", "val.*2", "val.*3"); - - assertThatExceptionOfType(AssertionError.class) - .isThrownBy(() -> assertions.valuesMatch("foo", ".*", "val.*5")) - .satisfies(ex -> assertThat(ex).hasMessage( - "Response header 'foo' has fewer or more values [value1, value2, value3] " + - "than number of patterns to match with [.*, val.*5]")); - - assertThatExceptionOfType(AssertionError.class) - .isThrownBy(() -> assertions.valuesMatch("foo", ".*", "val.*5", ".*")) - .satisfies(ex -> assertThat(ex).hasMessage( - "Response header 'foo'[1]='value2' does not match 'val.*5'")); - } - - @Test - void valueMatcher() { - HttpHeaders headers = new HttpHeaders(); - headers.add("foo", "bar"); - HeaderAssertions assertions = headerAssertions(headers); - - assertions.value("foo", containsString("a")); - } - - @Test - void valuesMatcher() { - HttpHeaders headers = new HttpHeaders(); - headers.add("foo", "bar"); - headers.add("foo", "baz"); - HeaderAssertions assertions = headerAssertions(headers); - - assertions.values("foo", hasItems("bar", "baz")); - } - - @Test - void exists() { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - HeaderAssertions assertions = headerAssertions(headers); - - // Success - assertions.exists("Content-Type"); - - // Header should not exist - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.exists("Framework")) - .satisfies(ex -> assertThat(ex).hasMessage("Response header 'Framework' does not exist")); - } - - @Test - void doesNotExist() { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.parseMediaType("application/json;charset=UTF-8")); - HeaderAssertions assertions = headerAssertions(headers); - - // Success - assertions.doesNotExist("Framework"); - - // Existing header - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.doesNotExist("Content-Type")) - .satisfies(ex -> assertThat(ex).hasMessage("Response header " + - "'Content-Type' exists with value=[application/json;charset=UTF-8]")); - } - - @Test - void contentTypeCompatibleWith() { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_XML); - HeaderAssertions assertions = headerAssertions(headers); - - // Success - assertions.contentTypeCompatibleWith(MediaType.parseMediaType("application/*")); - - // MediaTypes not compatible - assertThatExceptionOfType(AssertionError.class) - .isThrownBy(() -> assertions.contentTypeCompatibleWith(MediaType.TEXT_XML)) - .withMessage("Response header 'Content-Type'=[application/xml] is not compatible with [text/xml]"); - } - - @Test - void cacheControl() { - CacheControl control = CacheControl.maxAge(1, TimeUnit.HOURS).noTransform(); - - HttpHeaders headers = new HttpHeaders(); - headers.setCacheControl(control.getHeaderValue()); - HeaderAssertions assertions = headerAssertions(headers); - - // Success - assertions.cacheControl(control); - - // Wrong value - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.cacheControl(CacheControl.noStore())); - } - - @Test - void expires() { - HttpHeaders headers = new HttpHeaders(); - ZonedDateTime expires = ZonedDateTime.of(2018, 1, 1, 0, 0, 0, 0, ZoneId.of("UTC")); - headers.setExpires(expires); - HeaderAssertions assertions = headerAssertions(headers); - assertions.expires(expires.toInstant().toEpochMilli()); - - // Wrong value - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.expires(expires.toInstant().toEpochMilli() + 1)); - } - - @Test - void lastModified() { - HttpHeaders headers = new HttpHeaders(); - ZonedDateTime lastModified = ZonedDateTime.of(2018, 1, 1, 0, 0, 0, 0, ZoneId.of("UTC")); - headers.setLastModified(lastModified.toInstant().toEpochMilli()); - HeaderAssertions assertions = headerAssertions(headers); - assertions.lastModified(lastModified.toInstant().toEpochMilli()); - - // Wrong value - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.lastModified(lastModified.toInstant().toEpochMilli() + 1)); - } - - private HeaderAssertions headerAssertions(HttpHeaders responseHeaders) { - MockClientHttpRequest request = new MockClientHttpRequest(HttpMethod.GET, URI.create("/")); - MockClientHttpResponse response = new MockClientHttpResponse(HttpStatus.OK); - response.getHeaders().putAll(responseHeaders); - - ExchangeResult result = new ExchangeResult( - request, response, Mono.empty(), Mono.empty(), Duration.ZERO, null, null); - - return new HeaderAssertions(result, mock()); - } - -} 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 deleted file mode 100644 index bbb7842bca..0000000000 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/StatusAssertionTests.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright 2002-present 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 java.net.URI; -import java.time.Duration; - -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; - -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.mock.http.client.reactive.MockClientHttpRequest; -import org.springframework.mock.http.client.reactive.MockClientHttpResponse; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.greaterThan; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link StatusAssertions}. - * - * @author Rossen Stoyanchev - * @author Sam Brannen - */ -class StatusAssertionTests { - - @Test - void isEqualTo() { - StatusAssertions assertions = statusAssertions(HttpStatus.CONFLICT); - - // Success - assertions.isEqualTo(HttpStatus.CONFLICT); - assertions.isEqualTo(409); - - // Wrong status - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.isEqualTo(HttpStatus.REQUEST_TIMEOUT)); - - // Wrong status value - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.isEqualTo(408)); - } - - @Test // gh-23630, gh-29283 - void isEqualToWithCustomStatus() { - StatusAssertions assertions = statusAssertions(600); - - // Success - // assertions.isEqualTo(600); - - // Wrong status - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.isEqualTo(HttpStatus.REQUEST_TIMEOUT)); - - // Wrong status value - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.isEqualTo(408)); - } - - @Test - void reasonEquals() { - StatusAssertions assertions = statusAssertions(HttpStatus.CONFLICT); - - // Success - assertions.reasonEquals("Conflict"); - - // Wrong reason - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.reasonEquals("Request Timeout")); - } - - @Test - void statusSeries1xx() { - StatusAssertions assertions = statusAssertions(HttpStatus.CONTINUE); - - // Success - assertions.is1xxInformational(); - - // Wrong series - assertThatExceptionOfType(AssertionError.class).isThrownBy(assertions::is2xxSuccessful); - } - - @Test - void statusSeries2xx() { - StatusAssertions assertions = statusAssertions(HttpStatus.OK); - - // Success - assertions.is2xxSuccessful(); - - // Wrong series - assertThatExceptionOfType(AssertionError.class).isThrownBy(assertions::is5xxServerError); - } - - @Test - void statusSeries3xx() { - StatusAssertions assertions = statusAssertions(HttpStatus.PERMANENT_REDIRECT); - - // Success - assertions.is3xxRedirection(); - - // Wrong series - assertThatExceptionOfType(AssertionError.class).isThrownBy(assertions::is2xxSuccessful); - } - - @Test - void statusSeries4xx() { - StatusAssertions assertions = statusAssertions(HttpStatus.BAD_REQUEST); - - // Success - assertions.is4xxClientError(); - - // Wrong series - assertThatExceptionOfType(AssertionError.class).isThrownBy(assertions::is2xxSuccessful); - } - - @Test - void statusSeries5xx() { - StatusAssertions assertions = statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR); - - // Success - assertions.is5xxServerError(); - - // Wrong series - assertThatExceptionOfType(AssertionError.class).isThrownBy(assertions::is2xxSuccessful); - } - - @Test - void matchesStatusValue() { - StatusAssertions assertions = statusAssertions(HttpStatus.CONFLICT); - - // Success - assertions.value(equalTo(409)); - assertions.value(greaterThan(400)); - - // Wrong status - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.value(equalTo(200))); - } - - @Test // gh-26658 - void matchesCustomStatusValue() { - statusAssertions(600).value(equalTo(600)); - } - - - private StatusAssertions statusAssertions(HttpStatus status) { - return statusAssertions(status.value()); - } - - private StatusAssertions statusAssertions(int status) { - MockClientHttpRequest request = new MockClientHttpRequest(HttpMethod.GET, URI.create("/")); - MockClientHttpResponse response = new MockClientHttpResponse(status); - - ExchangeResult result = new ExchangeResult( - request, response, Mono.empty(), Mono.empty(), Duration.ZERO, null, null); - - return new StatusAssertions(result, mock()); - } - -} diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/client/StatusAssertionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/client/StatusAssertionTests.java deleted file mode 100644 index 099ca06f5b..0000000000 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/client/StatusAssertionTests.java +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright 2002-present 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.servlet.client; - -import java.io.IOException; - -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; -import org.springframework.mock.http.client.MockClientHttpRequest; -import org.springframework.web.client.RestClient; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.greaterThan; -import static org.mockito.BDDMockito.mock; -import static org.mockito.BDDMockito.when; - -/** - * Tests for {@link StatusAssertions}. - * - * @author Rob Worsnop - */ -class StatusAssertionTests { - - @Test - void isEqualTo() { - StatusAssertions assertions = statusAssertions(HttpStatus.CONFLICT); - - // Success - assertions.isEqualTo(HttpStatus.CONFLICT); - assertions.isEqualTo(409); - - // Wrong status - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.isEqualTo(HttpStatus.REQUEST_TIMEOUT)); - - // Wrong status value - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.isEqualTo(408)); - } - - @Test - void isEqualToWithCustomStatus() { - StatusAssertions assertions = statusAssertions(600); - - // Success - assertions.isEqualTo(600); - - // Wrong status - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - statusAssertions(601).isEqualTo(600)); - - } - - @Test - void reasonEquals() { - StatusAssertions assertions = statusAssertions(HttpStatus.CONFLICT); - - // Success - assertions.reasonEquals("Conflict"); - - // Wrong reason - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).reasonEquals("Conflict")); - } - - @Test - void statusSeries1xx() { - StatusAssertions assertions = statusAssertions(HttpStatus.CONTINUE); - - // Success - assertions.is1xxInformational(); - - // Wrong series - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - statusAssertions(HttpStatus.OK).is1xxInformational()); - } - - @Test - void statusSeries2xx() { - StatusAssertions assertions = statusAssertions(HttpStatus.OK); - - // Success - assertions.is2xxSuccessful(); - - // Wrong series - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).is2xxSuccessful()); - } - - @Test - void statusSeries3xx() { - StatusAssertions assertions = statusAssertions(HttpStatus.PERMANENT_REDIRECT); - - // Success - assertions.is3xxRedirection(); - - // Wrong series - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).is3xxRedirection()); - } - - @Test - void statusSeries4xx() { - StatusAssertions assertions = statusAssertions(HttpStatus.BAD_REQUEST); - - // Success - assertions.is4xxClientError(); - - // Wrong series - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).is4xxClientError()); - } - - @Test - void statusSeries5xx() { - StatusAssertions assertions = statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR); - - // Success - assertions.is5xxServerError(); - - // Wrong series - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - statusAssertions(HttpStatus.OK).is5xxServerError()); - } - - @Test - void matchesStatusValue() { - StatusAssertions assertions = statusAssertions(HttpStatus.CONFLICT); - - // Success - assertions.value(equalTo(409)); - assertions.value(greaterThan(400)); - - // Wrong status - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.value(equalTo(200))); - } - - @Test - void matchesCustomStatusValue() { - statusAssertions(600).value(equalTo(600)); - } - - @Test - void consumesStatusValue() { - StatusAssertions assertions = statusAssertions(HttpStatus.CONFLICT); - - // Success - assertions.value((Integer value) -> assertThat(value).isEqualTo(409)); - } - - @Test - void statusIsAccepted() { - // Success - statusAssertions(HttpStatus.ACCEPTED).isAccepted(); - - // Wrong status - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).isAccepted()); - } - - @Test - void statusIsNoContent() { - // Success - statusAssertions(HttpStatus.NO_CONTENT).isNoContent(); - - // Wrong status - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).isNoContent()); - } - - @Test - void statusIsFound() { - // Success - statusAssertions(HttpStatus.FOUND).isFound(); - - // Wrong status - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).isFound()); - } - - @Test - void statusIsSeeOther() { - // Success - statusAssertions(HttpStatus.SEE_OTHER).isSeeOther(); - - // Wrong status - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).isSeeOther()); - } - - @Test - void statusIsNotModified() { - // Success - statusAssertions(HttpStatus.NOT_MODIFIED).isNotModified(); - - // Wrong status - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).isNotModified()); - } - - @Test - void statusIsTemporaryRedirect() { - // Success - statusAssertions(HttpStatus.TEMPORARY_REDIRECT).isTemporaryRedirect(); - - // Wrong status - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).isTemporaryRedirect()); - } - - @Test - void statusIsPermanentRedirect() { - // Success - statusAssertions(HttpStatus.PERMANENT_REDIRECT).isPermanentRedirect(); - - // Wrong status - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).isPermanentRedirect()); - } - - @Test - void statusIsUnauthorized() { - // Success - statusAssertions(HttpStatus.UNAUTHORIZED).isUnauthorized(); - - // Wrong status - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).isUnauthorized()); - } - - @Test - void statusIsForbidden() { - // Success - statusAssertions(HttpStatus.FORBIDDEN).isForbidden(); - - // Wrong status - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> statusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).isForbidden()); - } - - private StatusAssertions statusAssertions(HttpStatus status) { - return statusAssertions(status.value()); - } - - private StatusAssertions statusAssertions(int status) { - try { - RestClient.RequestHeadersSpec.ConvertibleClientHttpResponse response = mock(); - when(response.getStatusCode()).thenReturn(HttpStatusCode.valueOf(status)); - when(response.getHeaders()).thenReturn(new HttpHeaders()); - ExchangeResult result = new ExchangeResult(new MockClientHttpRequest(), response, null); - return new StatusAssertions(result, mock()); - } - catch (IOException ex) { - throw new AssertionError(ex); - } - } - -} diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/client/CookieAssertionsTests.java b/spring-test/src/test/java/org/springframework/test/web/support/CookieAssertionsTests.java similarity index 73% rename from spring-test/src/test/java/org/springframework/test/web/servlet/client/CookieAssertionsTests.java rename to spring-test/src/test/java/org/springframework/test/web/support/CookieAssertionsTests.java index 4412adba02..6762e1cb8a 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/client/CookieAssertionsTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/support/CookieAssertionsTests.java @@ -14,51 +14,50 @@ * limitations under the License. */ -package org.springframework.test.web.servlet.client; +package org.springframework.test.web.support; import java.io.IOException; import java.time.Duration; +import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseCookie; -import org.springframework.mock.http.client.MockClientHttpRequest; -import org.springframework.web.client.RestClient; +import org.springframework.util.MultiValueMap; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.hamcrest.Matchers.equalTo; -import static org.mockito.BDDMockito.mock; -import static org.mockito.BDDMockito.when; /** - * Tests for {@link CookieAssertions} + * Tests for {@link AbstractCookieAssertions}. * * @author Rob Worsnop + * @author Rossen Stoyanchev */ public class CookieAssertionsTests { - private final ResponseCookie cookie = ResponseCookie.from("foo", "bar") - .maxAge(Duration.ofMinutes(30)) - .domain("foo.com") - .path("/foo") - .secure(true) - .httpOnly(true) - .partitioned(true) - .sameSite("Lax") - .build(); - - private CookieAssertions assertions; + private TestCookieAssertions assertions; @BeforeEach void setUp() throws IOException { - this.assertions = cookieAssertions(cookie); + + ResponseCookie cookie = ResponseCookie.from("foo", "bar") + .maxAge(Duration.ofMinutes(30)) + .domain("foo.com") + .path("/foo") + .secure(true) + .httpOnly(true) + .partitioned(true) + .sameSite("Lax") + .build(); + + this.assertions = initCookieAssertions(cookie); } + @Test void valueEquals() { assertions.valueEquals("foo", "bar"); @@ -75,7 +74,8 @@ public class CookieAssertionsTests { @Test void valueConsumer() { assertions.value("foo", input -> assertThat(input).isEqualTo("bar")); - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.value("foo", input -> assertThat(input).isEqualTo("what?!"))); + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> assertions.value("foo", input -> assertThat(input).isEqualTo("what?!"))); } @Test @@ -143,15 +143,31 @@ public class CookieAssertionsTests { assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.sameSite("foo", "Strict")); } + private TestCookieAssertions initCookieAssertions(ResponseCookie cookie) throws IOException { + return new TestCookieAssertions(cookie); + } - private CookieAssertions cookieAssertions(ResponseCookie cookie) throws IOException { - RestClient.RequestHeadersSpec.ConvertibleClientHttpResponse response = mock(); - var headers = new HttpHeaders(); - headers.set(HttpHeaders.SET_COOKIE, cookie.toString()); - when(response.getHeaders()).thenReturn(headers); - when(response.getStatusCode()).thenReturn(HttpStatusCode.valueOf(200)); - ExchangeResult result = new ExchangeResult(new MockClientHttpRequest(), response, null); - return new CookieAssertions(result, mock()); + + private static class TestCookieAssertions extends AbstractCookieAssertions { + + TestCookieAssertions(ResponseCookie cookie) { + super(new TestExchangeResult(cookie), ""); + } + + @Override + protected MultiValueMap getResponseCookies() { + ResponseCookie cookie = getExchangeResult().cookie(); + return MultiValueMap.fromSingleValue(Map.of(cookie.getName(), cookie)); + } + + @Override + protected void assertWithDiagnostics(Runnable assertion) { + assertion.run(); + } + } + + + private record TestExchangeResult(ResponseCookie cookie) { } } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/client/HeaderAssertionTests.java b/spring-test/src/test/java/org/springframework/test/web/support/HeaderAssertionTests.java similarity index 63% rename from spring-test/src/test/java/org/springframework/test/web/servlet/client/HeaderAssertionTests.java rename to spring-test/src/test/java/org/springframework/test/web/support/HeaderAssertionTests.java index 3dd307acab..01e6138ade 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/client/HeaderAssertionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/support/HeaderAssertionTests.java @@ -14,9 +14,8 @@ * limitations under the License. */ -package org.springframework.test.web.servlet.client; +package org.springframework.test.web.support; -import java.io.IOException; import java.net.URI; import java.time.ZoneId; import java.time.ZonedDateTime; @@ -27,21 +26,17 @@ import org.junit.jupiter.api.Test; import org.springframework.http.CacheControl; import org.springframework.http.ContentDisposition; import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatusCode; import org.springframework.http.MediaType; -import org.springframework.mock.http.client.MockClientHttpRequest; -import org.springframework.web.client.RestClient; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasItems; -import static org.mockito.BDDMockito.mock; -import static org.mockito.BDDMockito.when; /** - * Tests for {@link HeaderAssertions}. + * Tests for {@link AbstractHeaderAssertions}. * + * @author Rossen Stoyanchev * @author Rob Worsnop */ class HeaderAssertionTests { @@ -51,7 +46,7 @@ class HeaderAssertionTests { HttpHeaders headers = new HttpHeaders(); headers.add("foo", "bar"); headers.add("age", "22"); - HeaderAssertions assertions = headerAssertions(headers); + TestHeaderAssertions assertions = new TestHeaderAssertions(headers); // Success assertions.valueEquals("foo", "bar"); @@ -60,16 +55,16 @@ class HeaderAssertionTests { assertions.valueEquals("age", 22); // Missing header - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.valueEquals("what?!", "bar")); + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> assertions.valueEquals("what?!", "bar")); // Wrong value - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.valueEquals("foo", "what?!")); + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> assertions.valueEquals("foo", "what?!")); // Wrong # of values - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.valueEquals("foo", "bar", "what?!")); + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> assertions.valueEquals("foo", "bar", "what?!")); } @Test @@ -77,25 +72,25 @@ class HeaderAssertionTests { HttpHeaders headers = new HttpHeaders(); headers.add("foo", "bar"); headers.add("foo", "baz"); - HeaderAssertions assertions = headerAssertions(headers); + TestHeaderAssertions assertions = new TestHeaderAssertions(headers); // Success assertions.valueEquals("foo", "bar", "baz"); // Wrong value - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.valueEquals("foo", "bar", "what?!")); + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> assertions.valueEquals("foo", "bar", "what?!")); // Too few values - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.valueEquals("foo", "bar")); + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> assertions.valueEquals("foo", "bar")); } @Test void valueMatches() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.parseMediaType("application/json;charset=UTF-8")); - HeaderAssertions assertions = headerAssertions(headers); + TestHeaderAssertions assertions = new TestHeaderAssertions(headers); // Success assertions.valueMatches("Content-Type", ".*UTF-8.*"); @@ -112,7 +107,7 @@ class HeaderAssertionTests { void valueMatchesWithNonexistentHeader() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.parseMediaType("application/json;charset=UTF-8")); - HeaderAssertions assertions = headerAssertions(headers); + TestHeaderAssertions assertions = new TestHeaderAssertions(headers); assertThatExceptionOfType(AssertionError.class) .isThrownBy(() -> assertions.valueMatches("Content-XYZ", ".*ISO-8859-1.*")) @@ -125,7 +120,7 @@ class HeaderAssertionTests { headers.add("foo", "value1"); headers.add("foo", "value2"); headers.add("foo", "value3"); - HeaderAssertions assertions = headerAssertions(headers); + TestHeaderAssertions assertions = new TestHeaderAssertions(headers); assertions.valuesMatch("foo", "val.*1", "val.*2", "val.*3"); @@ -145,7 +140,7 @@ class HeaderAssertionTests { void valueMatcher() { HttpHeaders headers = new HttpHeaders(); headers.add("foo", "bar"); - HeaderAssertions assertions = headerAssertions(headers); + TestHeaderAssertions assertions = new TestHeaderAssertions(headers); assertions.value("foo", containsString("a")); } @@ -155,7 +150,7 @@ class HeaderAssertionTests { HttpHeaders headers = new HttpHeaders(); headers.add("foo", "bar"); headers.add("foo", "baz"); - HeaderAssertions assertions = headerAssertions(headers); + TestHeaderAssertions assertions = new TestHeaderAssertions(headers); assertions.values("foo", hasItems("bar", "baz")); } @@ -164,38 +159,38 @@ class HeaderAssertionTests { void exists() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); - HeaderAssertions assertions = headerAssertions(headers); + TestHeaderAssertions assertions = new TestHeaderAssertions(headers); // Success assertions.exists("Content-Type"); // Header should not exist - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.exists("Framework")) - .satisfies(ex -> assertThat(ex).hasMessage("Response header 'Framework' does not exist")); + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> assertions.exists("Framework")) + .satisfies(ex -> assertThat(ex).hasMessage("Response header 'Framework' does not exist")); } @Test void doesNotExist() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.parseMediaType("application/json;charset=UTF-8")); - HeaderAssertions assertions = headerAssertions(headers); + TestHeaderAssertions assertions = new TestHeaderAssertions(headers); // Success assertions.doesNotExist("Framework"); // Existing header - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.doesNotExist("Content-Type")) - .satisfies(ex -> assertThat(ex).hasMessage("Response header " + - "'Content-Type' exists with value=[application/json;charset=UTF-8]")); + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> assertions.doesNotExist("Content-Type")) + .satisfies(ex -> assertThat(ex).hasMessage("Response header " + + "'Content-Type' exists with value=[application/json;charset=UTF-8]")); } @Test void contentTypeCompatibleWith() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_XML); - HeaderAssertions assertions = headerAssertions(headers); + TestHeaderAssertions assertions = new TestHeaderAssertions(headers); // Success assertions.contentTypeCompatibleWith(MediaType.parseMediaType("application/*")); @@ -203,22 +198,21 @@ class HeaderAssertionTests { // MediaTypes not compatible assertThatExceptionOfType(AssertionError.class) - .isThrownBy(() -> assertions.contentTypeCompatibleWith(MediaType.TEXT_XML)) - .withMessage("Response header 'Content-Type'=[application/xml] is not compatible with [text/xml]"); + .isThrownBy(() -> assertions.contentTypeCompatibleWith(MediaType.TEXT_XML)) + .withMessage("Response header 'Content-Type'=[application/xml] is not compatible with [text/xml]"); } @Test void location() { HttpHeaders headers = new HttpHeaders(); headers.setLocation(URI.create("http://localhost:8080/")); - HeaderAssertions assertions = headerAssertions(headers); + TestHeaderAssertions assertions = new TestHeaderAssertions(headers); // Success assertions.location("http://localhost:8080/"); // Wrong value - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.location("http://localhost:8081/")); + assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.location("http://localhost:8081/")); } @Test @@ -227,51 +221,50 @@ class HeaderAssertionTests { HttpHeaders headers = new HttpHeaders(); headers.setCacheControl(control.getHeaderValue()); - HeaderAssertions assertions = headerAssertions(headers); + TestHeaderAssertions assertions = new TestHeaderAssertions(headers); // Success assertions.cacheControl(control); // Wrong value - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.cacheControl(CacheControl.noStore())); + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> assertions.cacheControl(CacheControl.noStore())); } @Test void contentDisposition() { HttpHeaders headers = new HttpHeaders(); headers.setContentDispositionFormData("foo", "bar"); - HeaderAssertions assertions = headerAssertions(headers); + TestHeaderAssertions assertions = new TestHeaderAssertions(headers); assertions.contentDisposition(ContentDisposition.formData().name("foo").filename("bar").build()); // Wrong value - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.contentDisposition(ContentDisposition.attachment().build())); + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> assertions.contentDisposition(ContentDisposition.attachment().build())); } @Test void contentLength() { HttpHeaders headers = new HttpHeaders(); headers.setContentLength(100); - HeaderAssertions assertions = headerAssertions(headers); + TestHeaderAssertions assertions = new TestHeaderAssertions(headers); assertions.contentLength(100); // Wrong value - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.contentLength(200)); + assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.contentLength(200)); } @Test void contentType() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); - HeaderAssertions assertions = headerAssertions(headers); + TestHeaderAssertions assertions = new TestHeaderAssertions(headers); assertions.contentType(MediaType.APPLICATION_JSON); assertions.contentType("application/json"); // Wrong value - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.contentType(MediaType.APPLICATION_XML)); + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> assertions.contentType(MediaType.APPLICATION_XML)); } @@ -280,12 +273,12 @@ class HeaderAssertionTests { HttpHeaders headers = new HttpHeaders(); ZonedDateTime expires = ZonedDateTime.of(2018, 1, 1, 0, 0, 0, 0, ZoneId.of("UTC")); headers.setExpires(expires); - HeaderAssertions assertions = headerAssertions(headers); + TestHeaderAssertions assertions = new TestHeaderAssertions(headers); assertions.expires(expires.toInstant().toEpochMilli()); // Wrong value - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.expires(expires.toInstant().toEpochMilli() + 1)); + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> assertions.expires(expires.toInstant().toEpochMilli() + 1)); } @Test @@ -293,37 +286,45 @@ class HeaderAssertionTests { HttpHeaders headers = new HttpHeaders(); ZonedDateTime lastModified = ZonedDateTime.of(2018, 1, 1, 0, 0, 0, 0, ZoneId.of("UTC")); headers.setLastModified(lastModified.toInstant().toEpochMilli()); - HeaderAssertions assertions = headerAssertions(headers); + TestHeaderAssertions assertions = new TestHeaderAssertions(headers); assertions.lastModified(lastModified.toInstant().toEpochMilli()); // Wrong value - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.lastModified(lastModified.toInstant().toEpochMilli() + 1)); + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> assertions.lastModified(lastModified.toInstant().toEpochMilli() + 1)); } @Test void equalsDate() { HttpHeaders headers = new HttpHeaders(); headers.setDate("foo", 1000); - HeaderAssertions assertions = headerAssertions(headers); + TestHeaderAssertions assertions = new TestHeaderAssertions(headers); assertions.valueEqualsDate("foo", 1000); // Wrong value - assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> - assertions.valueEqualsDate("foo", 2000)); + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> assertions.valueEqualsDate("foo", 2000)); } - private HeaderAssertions headerAssertions(HttpHeaders responseHeaders) { - try { - RestClient.RequestHeadersSpec.ConvertibleClientHttpResponse response = mock(); - when(response.getStatusCode()).thenReturn(HttpStatusCode.valueOf(200)); - when(response.getHeaders()).thenReturn(responseHeaders); - ExchangeResult result = new ExchangeResult(new MockClientHttpRequest(), response, null); - return new HeaderAssertions(result, mock()); + + private static class TestHeaderAssertions extends AbstractHeaderAssertions { + + TestHeaderAssertions(HttpHeaders headers) { + super(new TestExchangeResult(headers), ""); } - catch (IOException ex) { - throw new IllegalStateException(ex); + + @Override + protected HttpHeaders getResponseHeaders() { + return getExchangeResult().headers(); + } + + @Override + protected void assertWithDiagnostics(Runnable assertion) { + assertion.run(); } } + + private record TestExchangeResult(HttpHeaders headers) {} + } diff --git a/spring-test/src/test/java/org/springframework/test/web/support/StatusAssertionTests.java b/spring-test/src/test/java/org/springframework/test/web/support/StatusAssertionTests.java new file mode 100644 index 0000000000..72b3904ca0 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/support/StatusAssertionTests.java @@ -0,0 +1,280 @@ +/* + * Copyright 2002-present 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.support; + +import org.junit.jupiter.api.Test; + +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; + +/** + * Tests for {@link AbstractStatusAssertions}. + * + * @author Rossen Stoyanchev + * @author Rob Worsnop + */ +class StatusAssertionTests { + + @Test + void isEqualTo() { + TestStatusAssertions assertions = new TestStatusAssertions(HttpStatus.CONFLICT); + + // Success + assertions.isEqualTo(HttpStatus.CONFLICT); + assertions.isEqualTo(409); + + // Wrong status + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> assertions.isEqualTo(HttpStatus.REQUEST_TIMEOUT)); + + // Wrong status value + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> assertions.isEqualTo(408)); + } + + @Test + void isEqualToWithCustomStatus() { + TestStatusAssertions assertions = new TestStatusAssertions(600); + + // Success + assertions.isEqualTo(600); + + // Wrong status + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> new TestStatusAssertions(601).isEqualTo(600)); + + } + + @Test + void reasonEquals() { + TestStatusAssertions assertions = new TestStatusAssertions(HttpStatus.CONFLICT); + + // Success + assertions.reasonEquals("Conflict"); + + // Wrong reason + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> new TestStatusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).reasonEquals("Conflict")); + } + + @Test + void statusSeries1xx() { + TestStatusAssertions assertions = new TestStatusAssertions(HttpStatus.CONTINUE); + + // Success + assertions.is1xxInformational(); + + // Wrong series + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> new TestStatusAssertions(HttpStatus.OK).is1xxInformational()); + } + + @Test + void statusSeries2xx() { + TestStatusAssertions assertions = new TestStatusAssertions(HttpStatus.OK); + + // Success + assertions.is2xxSuccessful(); + + // Wrong series + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> new TestStatusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).is2xxSuccessful()); + } + + @Test + void statusSeries3xx() { + TestStatusAssertions assertions = new TestStatusAssertions(HttpStatus.PERMANENT_REDIRECT); + + // Success + assertions.is3xxRedirection(); + + // Wrong series + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> new TestStatusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).is3xxRedirection()); + } + + @Test + void statusSeries4xx() { + TestStatusAssertions assertions = new TestStatusAssertions(HttpStatus.BAD_REQUEST); + + // Success + assertions.is4xxClientError(); + + // Wrong series + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> new TestStatusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).is4xxClientError()); + } + + @Test + void statusSeries5xx() { + TestStatusAssertions assertions = new TestStatusAssertions(HttpStatus.INTERNAL_SERVER_ERROR); + + // Success + assertions.is5xxServerError(); + + // Wrong series + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> new TestStatusAssertions(HttpStatus.OK).is5xxServerError()); + } + + @Test + void matchesStatusValue() { + TestStatusAssertions assertions = new TestStatusAssertions(HttpStatus.CONFLICT); + + // Success + assertions.value(equalTo(409)); + assertions.value(greaterThan(400)); + + // Wrong status + assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertions.value(equalTo(200))); + } + + @Test + void matchesCustomStatusValue() { + new TestStatusAssertions(600).value(equalTo(600)); + } + + @Test + void consumesStatusValue() { + TestStatusAssertions assertions = new TestStatusAssertions(HttpStatus.CONFLICT); + + // Success + assertions.value((Integer value) -> assertThat(value).isEqualTo(409)); + } + + @Test + void statusIsAccepted() { + // Success + new TestStatusAssertions(HttpStatus.ACCEPTED).isAccepted(); + + // Wrong status + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> new TestStatusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).isAccepted()); + } + + @Test + void statusIsNoContent() { + // Success + new TestStatusAssertions(HttpStatus.NO_CONTENT).isNoContent(); + + // Wrong status + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> new TestStatusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).isNoContent()); + } + + @Test + void statusIsFound() { + // Success + new TestStatusAssertions(HttpStatus.FOUND).isFound(); + + // Wrong status + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> new TestStatusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).isFound()); + } + + @Test + void statusIsSeeOther() { + // Success + new TestStatusAssertions(HttpStatus.SEE_OTHER).isSeeOther(); + + // Wrong status + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> new TestStatusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).isSeeOther()); + } + + @Test + void statusIsNotModified() { + // Success + new TestStatusAssertions(HttpStatus.NOT_MODIFIED).isNotModified(); + + // Wrong status + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> new TestStatusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).isNotModified()); + } + + @Test + void statusIsTemporaryRedirect() { + // Success + new TestStatusAssertions(HttpStatus.TEMPORARY_REDIRECT).isTemporaryRedirect(); + + // Wrong status + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> new TestStatusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).isTemporaryRedirect()); + } + + @Test + void statusIsPermanentRedirect() { + // Success + new TestStatusAssertions(HttpStatus.PERMANENT_REDIRECT).isPermanentRedirect(); + + // Wrong status + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> new TestStatusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).isPermanentRedirect()); + } + + @Test + void statusIsUnauthorized() { + // Success + new TestStatusAssertions(HttpStatus.UNAUTHORIZED).isUnauthorized(); + + // Wrong status + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> new TestStatusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).isUnauthorized()); + } + + @Test + void statusIsForbidden() { + // Success + new TestStatusAssertions(HttpStatus.FORBIDDEN).isForbidden(); + + // Wrong status + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> new TestStatusAssertions(HttpStatus.INTERNAL_SERVER_ERROR).isForbidden()); + } + + + private static class TestStatusAssertions extends AbstractStatusAssertions { + + TestStatusAssertions(HttpStatus status) { + this(status.value()); + } + + TestStatusAssertions(int status) { + super(new TestExchangeResult(HttpStatusCode.valueOf(status)), ""); + } + + @Override + protected HttpStatusCode getStatus() { + return getExchangeResult().status(); + } + + @Override + protected void assertWithDiagnostics(Runnable assertion) { + assertion.run(); + } + } + + + private record TestExchangeResult(HttpStatusCode status) { + } + +} diff --git a/src/checkstyle/checkstyle-suppressions.xml b/src/checkstyle/checkstyle-suppressions.xml index 7b93a7ca85..da63723d68 100644 --- a/src/checkstyle/checkstyle-suppressions.xml +++ b/src/checkstyle/checkstyle-suppressions.xml @@ -98,7 +98,7 @@ - +