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] =?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);