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