From bcecce7aace32538badce20b9ee8b0d03da12114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Mon, 6 May 2024 17:33:30 +0200 Subject: [PATCH] Add shortcuts for frequently used assertions See gh-32712 --- .../AbstractHttpServletResponseAssert.java | 105 ++++++++++++++++-- .../servlet/assertj/MvcTestResultAssert.java | 10 -- ...bstractHttpServletResponseAssertTests.java | 59 +++++++++- .../MockMvcTesterIntegrationTests.java | 14 +-- 4 files changed, 157 insertions(+), 31 deletions(-) diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/AbstractHttpServletResponseAssert.java b/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/AbstractHttpServletResponseAssert.java index ddba8a187f6b..fe079f7a9d86 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/AbstractHttpServletResponseAssert.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/AbstractHttpServletResponseAssert.java @@ -28,7 +28,9 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus.Series; +import org.springframework.http.MediaType; import org.springframework.test.http.HttpHeadersAssert; +import org.springframework.test.http.MediaTypeAssert; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.function.SingletonSupplier; @@ -48,15 +50,24 @@ public abstract class AbstractHttpServletResponseAssert, ACTUAL> extends AbstractObjectAssert { - private final Supplier> statusAssert; + private final Supplier contentTypeAssertSupplier; private final Supplier headersAssertSupplier; + private final Supplier> statusAssert; + protected AbstractHttpServletResponseAssert(ACTUAL actual, Class selfType) { super(actual, selfType); - this.statusAssert = SingletonSupplier.of(() -> Assertions.assertThat(getResponse().getStatus()).as("HTTP status code")); + this.contentTypeAssertSupplier = SingletonSupplier.of(() -> new MediaTypeAssert(getResponse().getContentType())); this.headersAssertSupplier = SingletonSupplier.of(() -> new HttpHeadersAssert(getHttpHeaders(getResponse()))); + this.statusAssert = SingletonSupplier.of(() -> Assertions.assertThat(getResponse().getStatus()).as("HTTP status code")); + } + + private static HttpHeaders getHttpHeaders(HttpServletResponse response) { + MultiValueMap headers = new LinkedMultiValueMap<>(); + response.getHeaderNames().forEach(name -> headers.put(name, new ArrayList<>(response.getHeaders(name)))); + return new HttpHeaders(headers); } /** @@ -67,6 +78,14 @@ protected AbstractHttpServletResponseAssert(ACTUAL actual, Class selfType) { */ protected abstract R getResponse(); + /** + * Return a new {@linkplain MediaTypeAssert assertion} object that uses the + * response's {@linkplain MediaType content type} as the object to test. + */ + public MediaTypeAssert contentType() { + return this.contentTypeAssertSupplier.get(); + } + /** * Return a new {@linkplain HttpHeadersAssert assertion} object that uses * {@link HttpHeaders} as the object to test. The returned assertion @@ -84,6 +103,82 @@ public HttpHeadersAssert headers() { return this.headersAssertSupplier.get(); } + // Content-type shortcuts + + /** + * Verify that the response's {@code Content-Type} is equal to the given value. + * @param contentType the expected content type + */ + public SELF hasContentType(MediaType contentType) { + contentType().isEqualTo(contentType); + return this.myself; + } + + /** + * Verify that the response's {@code Content-Type} is equal to the given + * string representation. + * @param contentType the expected content type + */ + public SELF hasContentType(String contentType) { + contentType().isEqualTo(contentType); + return this.myself; + } + + /** + * Verify that the response's {@code Content-Type} is + * {@linkplain MediaType#isCompatibleWith(MediaType) compatible} with the + * given value. + * @param contentType the expected compatible content type + */ + public SELF hasContentTypeCompatibleWith(MediaType contentType) { + contentType().isCompatibleWith(contentType); + return this.myself; + } + + /** + * Verify that the response's {@code Content-Type} is + * {@linkplain MediaType#isCompatibleWith(MediaType) compatible} with the + * given string representation. + * @param contentType the expected compatible content type + */ + public SELF hasContentTypeCompatibleWith(String contentType) { + contentType().isCompatibleWith(contentType); + return this.myself; + } + + // Headers shortcuts + + /** + * Verify that the response contains a header with the given {@code name}. + * @param name the name of an expected HTTP header + */ + public SELF containsHeader(String name) { + headers().containsHeader(name); + return this.myself; + } + + /** + * Verify that the response does not contain a header with the given {@code name}. + * @param name the name of an HTTP header that should not be present + */ + public SELF doesNotContainHeader(String name) { + headers().doesNotContainHeader(name); + return this.myself; + } + + /** + * Verify that the response contains a header with the given {@code name} + * and primary {@code value}. + * @param name the name of an expected HTTP header + * @param value the expected value of the header + */ + public SELF hasHeader(String name, String value) { + headers().hasValue(name, value); + return this.myself; + } + + // Status + /** * Verify that the HTTP status is equal to the specified status code. * @param status the expected HTTP status code @@ -159,10 +254,4 @@ private AbstractIntegerAssert status() { return this.statusAssert.get(); } - private static HttpHeaders getHttpHeaders(HttpServletResponse response) { - MultiValueMap headers = new LinkedMultiValueMap<>(); - response.getHeaderNames().forEach(name -> headers.put(name, new ArrayList<>(response.getHeaders(name)))); - return new HttpHeaders(headers); - } - } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MvcTestResultAssert.java b/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MvcTestResultAssert.java index ca1dd65c4ace..7c7ff332ec86 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MvcTestResultAssert.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MvcTestResultAssert.java @@ -30,12 +30,10 @@ import org.assertj.core.error.BasicErrorMessageFactory; import org.assertj.core.internal.Failures; -import org.springframework.http.MediaType; import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.lang.Nullable; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.test.http.MediaTypeAssert; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultHandler; import org.springframework.test.web.servlet.ResultMatcher; @@ -87,14 +85,6 @@ public CookieMapAssert cookies() { return new CookieMapAssert(getMvcResult().getResponse().getCookies()); } - /** - * Return a new {@linkplain MediaTypeAssert assertion} object that uses the - * response's {@linkplain MediaType content type} as the object to test. - */ - public MediaTypeAssert contentType() { - return new MediaTypeAssert(getMvcResult().getResponse().getContentType()); - } - /** * Return a new {@linkplain HandlerResultAssert assertion} object that uses * the handler as the object to test. For a method invocation on a diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/AbstractHttpServletResponseAssertTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/AbstractHttpServletResponseAssertTests.java index 0d28a4ea9343..1344ada36537 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/AbstractHttpServletResponseAssertTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/AbstractHttpServletResponseAssertTests.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletResponse; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -37,13 +38,30 @@ class AbstractHttpServletResponseAssertTests { @Nested class HeadersTests { + @Test + void containsHeader() { + MockHttpServletResponse response = createResponse(Map.of("n1", "v1", "n2", "v2", "n3", "v3")); + assertThat(response).containsHeader("n1"); + } + + @Test + void doesNotContainHeader() { + MockHttpServletResponse response = createResponse(Map.of("n1", "v1", "n2", "v2", "n3", "v3")); + assertThat(response).doesNotContainHeader("n4"); + } + + @Test + void hasHeader() { + MockHttpServletResponse response = createResponse(Map.of("n1", "v1", "n2", "v2", "n3", "v3")); + assertThat(response).hasHeader("n1", "v1"); + } + @Test void headersAreMatching() { MockHttpServletResponse response = createResponse(Map.of("n1", "v1", "n2", "v2", "n3", "v3")); assertThat(response).headers().containsHeaders("n1", "n2", "n3"); } - private MockHttpServletResponse createResponse(Map headers) { MockHttpServletResponse response = new MockHttpServletResponse(); headers.forEach(response::addHeader); @@ -51,6 +69,45 @@ private MockHttpServletResponse createResponse(Map headers) { } } + @Nested + class ContentTypeTests { + + @Test + void contentType() { + MockHttpServletResponse response = createResponse("text/plain"); + assertThat(response).hasContentType(MediaType.TEXT_PLAIN); + } + + @Test + void contentTypeAndRepresentation() { + MockHttpServletResponse response = createResponse("text/plain"); + assertThat(response).hasContentType("text/plain"); + } + + @Test + void contentTypeCompatibleWith() { + MockHttpServletResponse response = createResponse("application/json;charset=UTF-8"); + assertThat(response).hasContentTypeCompatibleWith(MediaType.APPLICATION_JSON); + } + + @Test + void contentTypeCompatibleWithAndStringRepresentation() { + MockHttpServletResponse response = createResponse("text/plain"); + assertThat(response).hasContentTypeCompatibleWith("text/*"); + } + + @Test + void contentTypeCanBeAsserted() { + MockHttpServletResponse response = createResponse("text/plain"); + assertThat(response).contentType().isInstanceOf(MediaType.class).isCompatibleWith("text/*").isNotNull(); + } + + private MockHttpServletResponse createResponse(String contentType) { + MockHttpServletResponse response = new MockHttpServletResponse(); + response.setContentType(contentType); + return response; + } + } @Nested class StatusTests { diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/MockMvcTesterIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/MockMvcTesterIntegrationTests.java index 4d58ad5c37a5..1a6f69daa3f6 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/MockMvcTesterIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/MockMvcTesterIntegrationTests.java @@ -138,16 +138,6 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons } } - @Nested - class ContentTypeTests { - - @Test - void contentType() { - assertThat(perform(get("/greet"))).contentType().isCompatibleWith("text/plain"); - } - - } - @Nested class StatusTests { @@ -168,8 +158,8 @@ class HeadersTests { @Test void shouldAssertHeader() { - assertThat(perform(get("/greet"))).headers() - .hasValue("Content-Type", "text/plain;charset=ISO-8859-1"); + assertThat(perform(get("/greet"))) + .hasHeader("Content-Type", "text/plain;charset=ISO-8859-1"); } @Test