From 7cdb3266232bb59b3f73d62b771417fcc9e697f5 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Thu, 3 Sep 2026 12:56:32 +0200 Subject: [PATCH 1/3] =?UTF-8?q?Declare=20redirectedUrl=20argument=20as=20@?= =?UTF-8?q?=E2=81=A0Nullable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MockMvcResultMatchers.forwardedUrl() already accepts a @⁠Nullable expected value to assert that no forwarding occurred, but its counterpart redirectedUrl() previously did not, even though the underlying assertEquals() comparison is null-safe and behaves the same way for redirects. To address that, this commit adds the same @⁠Nullable declaration to redirectedUrl() and documents the null semantics in the Javadoc for both methods. Closes gh-37230 --- .../servlet/result/MockMvcResultMatchers.java | 8 +++-- .../result/MockMvcResultMatchersTests.java | 34 +++++++++++++++++-- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/result/MockMvcResultMatchers.java b/spring-test/src/main/java/org/springframework/test/web/servlet/result/MockMvcResultMatchers.java index 60122452826..b5cbd3a1563 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/result/MockMvcResultMatchers.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/result/MockMvcResultMatchers.java @@ -85,7 +85,8 @@ public abstract class MockMvcResultMatchers { /** * Asserts the request was forwarded to the given URL. *

This method accepts only exact matches. - * @param expectedUrl the exact URL expected + * @param expectedUrl the exact URL expected; or {@code null} to assert + * that no forwarding occurred */ public static ResultMatcher forwardedUrl(@Nullable String expectedUrl) { return result -> assertEquals("Forwarded URL", expectedUrl, result.getResponse().getForwardedUrl()); @@ -124,9 +125,10 @@ public abstract class MockMvcResultMatchers { /** * Asserts the request was redirected to the given URL. *

This method accepts only exact matches. - * @param expectedUrl the exact URL expected + * @param expectedUrl the exact URL expected; or {@code null} to assert + * that no redirect occurred */ - public static ResultMatcher redirectedUrl(String expectedUrl) { + public static ResultMatcher redirectedUrl(@Nullable String expectedUrl) { return result -> assertEquals("Redirected URL", expectedUrl, result.getResponse().getRedirectedUrl()); } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/result/MockMvcResultMatchersTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/result/MockMvcResultMatchersTests.java index bae29cbf992..7e1f4993f5c 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/result/MockMvcResultMatchersTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/result/MockMvcResultMatchersTests.java @@ -39,11 +39,17 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. class MockMvcResultMatchersTests { @Test - void redirect() throws Exception { + void redirect() { assertThatCode(() -> redirectedUrl("/resource/1").match(redirectedUrlStub("/resource/1"))) .doesNotThrowAnyException(); } + @Test // gh-37230 + void redirectWithNullAssertsNoRedirectOccurred() { + assertThatCode(() -> redirectedUrl(null).match(noRedirectOrForwardStub())) + .doesNotThrowAnyException(); + } + @Test void redirectNonMatching() { assertThatExceptionOfType(AssertionError.class) @@ -58,6 +64,13 @@ class MockMvcResultMatchersTests { .withMessageEndingWith("expected: but was:"); } + @Test // gh-37230 + void redirectWithNullNonMatchingBecauseRedirectOccurred() { + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> redirectedUrl(null).match(redirectedUrlStub("/resource/1"))) + .withMessageEndingWith("expected: but was:"); + } + @Test void redirectWithUrlTemplate() { assertThatCode(() -> redirectedUrlTemplate("/orders/{orderId}/items/{itemId}", 1, 2).match(redirectedUrlStub("/orders/1/items/2"))) @@ -85,11 +98,17 @@ class MockMvcResultMatchersTests { } @Test - void forward() throws Exception { + void forward() { assertThatCode(() -> forwardedUrl("/api/resource/1").match(forwardedUrlStub("/api/resource/1"))) .doesNotThrowAnyException(); } + @Test // gh-22155 + void forwardWithNullAssertsNoForwardingOccurred() { + assertThatCode(() -> forwardedUrl(null).match(noRedirectOrForwardStub())) + .doesNotThrowAnyException(); + } + @Test void forwardNonMatching() { assertThatExceptionOfType(AssertionError.class) @@ -104,6 +123,13 @@ class MockMvcResultMatchersTests { .withMessage("Forwarded URL 'null' does not match the expected URL pattern '/resource/*'"); } + @Test // gh-22155 + void forwardWithNullNonMatchingBecauseForwardingOccurred() { + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> forwardedUrl(null).match(forwardedUrlStub("/resource/1"))) + .withMessageEndingWith("expected: but was:"); + } + @Test void forwardWithQueryString() { assertThatCode(() -> forwardedUrl("/api/resource/1?arg=value").match(forwardedUrlStub("/api/resource/1?arg=value"))) @@ -136,6 +162,10 @@ class MockMvcResultMatchersTests { .withMessage("Forwarded URL 'null' does not match the expected URL pattern '/resource/*'"); } + private StubMvcResult noRedirectOrForwardStub() { + return new StubMvcResult(null, null, null, null, null, null, new MockHttpServletResponse()); + } + private StubMvcResult redirectedUrlStub(String redirectUrl) throws Exception { MockHttpServletResponse response = new MockHttpServletResponse(); response.sendRedirect(redirectUrl); From 4a92dd6ed186ed06f8b1034dd94c5de43821f5b5 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Thu, 3 Sep 2026 13:19:21 +0200 Subject: [PATCH 2/3] Reorder tests --- .../servlet/result/MockMvcResultMatchersTests.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/result/MockMvcResultMatchersTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/result/MockMvcResultMatchersTests.java index 7e1f4993f5c..d734465c391 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/result/MockMvcResultMatchersTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/result/MockMvcResultMatchersTests.java @@ -103,6 +103,12 @@ class MockMvcResultMatchersTests { .doesNotThrowAnyException(); } + @Test + void forwardWithQueryString() { + assertThatCode(() -> forwardedUrl("/api/resource/1?arg=value").match(forwardedUrlStub("/api/resource/1?arg=value"))) + .doesNotThrowAnyException(); + } + @Test // gh-22155 void forwardWithNullAssertsNoForwardingOccurred() { assertThatCode(() -> forwardedUrl(null).match(noRedirectOrForwardStub())) @@ -130,12 +136,6 @@ class MockMvcResultMatchersTests { .withMessageEndingWith("expected: but was:"); } - @Test - void forwardWithQueryString() { - assertThatCode(() -> forwardedUrl("/api/resource/1?arg=value").match(forwardedUrlStub("/api/resource/1?arg=value"))) - .doesNotThrowAnyException(); - } - @Test void forwardWithUrlTemplate() { assertThatCode(() -> forwardedUrlTemplate("/orders/{orderId}/items/{itemId}", 1, 2).match(forwardedUrlStub("/orders/1/items/2"))) From 34ed7a5e2262a0999b41215aa8f805c32780245b Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Thu, 3 Sep 2026 13:20:29 +0200 Subject: [PATCH 3/3] Test correct scenarios in tests --- .../web/servlet/result/MockMvcResultMatchersTests.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/result/MockMvcResultMatchersTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/result/MockMvcResultMatchersTests.java index d734465c391..42570aa3d0f 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/result/MockMvcResultMatchersTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/result/MockMvcResultMatchersTests.java @@ -118,15 +118,15 @@ class MockMvcResultMatchersTests { @Test void forwardNonMatching() { assertThatExceptionOfType(AssertionError.class) - .isThrownBy(() -> forwardedUrlPattern("api/resource/2").match(forwardedUrlStub("api/resource/1"))) - .withMessage("'api/resource/2' is not an Ant-style path pattern"); + .isThrownBy(() -> forwardedUrl("/api/resource/2").match(forwardedUrlStub("/api/resource/1"))) + .withMessageEndingWith("expected: but was:"); } @Test void forwardNonMatchingBecauseNotForward() { assertThatExceptionOfType(AssertionError.class) - .isThrownBy(() -> forwardedUrlPattern("/resource/*").match(redirectedUrlStub("/resource/1"))) - .withMessage("Forwarded URL 'null' does not match the expected URL pattern '/resource/*'"); + .isThrownBy(() -> forwardedUrl("/resource/1").match(redirectedUrlStub("/resource/1"))) + .withMessageEndingWith("expected: but was:"); } @Test // gh-22155