From d6e35cf1f0dda3b5d2dbc6b26d4975858372982f Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Thu, 3 Apr 2025 14:43:23 +0200 Subject: [PATCH] Introduce queryParamCount() in MockRestRequestMatchers Closes gh-34703 --- .../client/match/MockRestRequestMatchers.java | 25 +++++++++++++-- .../match/MockRestRequestMatchersTests.java | 31 +++++++++++++++++-- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/spring-test/src/main/java/org/springframework/test/web/client/match/MockRestRequestMatchers.java b/spring-test/src/main/java/org/springframework/test/web/client/match/MockRestRequestMatchers.java index 8a72d2b99db..f28300c382b 100644 --- a/spring-test/src/main/java/org/springframework/test/web/client/match/MockRestRequestMatchers.java +++ b/spring-test/src/main/java/org/springframework/test/web/client/match/MockRestRequestMatchers.java @@ -19,6 +19,7 @@ package org.springframework.test.web.client.match; import java.net.URI; import java.util.List; import java.util.Map; +import java.util.Set; import javax.xml.xpath.XPathExpressionException; @@ -130,6 +131,7 @@ public abstract class MockRestRequestMatchers { * @since 5.3.27 * @see #queryParam(String, Matcher...) * @see #queryParam(String, String...) + * @see #queryParamCount(int) */ public static RequestMatcher queryParamList(String name, Matcher> matcher) { return request -> { @@ -158,6 +160,7 @@ public abstract class MockRestRequestMatchers { * parameter value * @see #queryParamList(String, Matcher) * @see #queryParam(String, String...) + * @see #queryParamCount(int) */ @SafeVarargs @SuppressWarnings("NullAway") // Dataflow analysis limitation @@ -187,6 +190,7 @@ public abstract class MockRestRequestMatchers { * parameter value * @see #queryParamList(String, Matcher) * @see #queryParam(String, Matcher...) + * @see #queryParamCount(int) */ @SuppressWarnings("NullAway") // Dataflow analysis limitation public static RequestMatcher queryParam(String name, String... expectedValues) { @@ -199,6 +203,25 @@ public abstract class MockRestRequestMatchers { }; } + /** + * Assert the number of query parameters present in the request. + * @param expectedCount the number of expected query parameters + * @since 7.0 + * @see #queryParamList(String, Matcher) + * @see #queryParam(String, Matcher...) + * @see #queryParam(String, String...) + */ + @SuppressWarnings("NullAway") // Dataflow analysis limitation + public static RequestMatcher queryParamCount(int expectedCount) { + return request -> { + Set parameterNames = getQueryParams(request).keySet(); + int actualCount = parameterNames.size(); + if (expectedCount != actualCount) { + fail("Expected %d query parameter(s) but found %d: %s".formatted(expectedCount, actualCount, parameterNames)); + } + }; + } + private static MultiValueMap getQueryParams(ClientHttpRequest request) { return UriComponentsBuilder.fromUri(request.getURI()).build().getQueryParams(); } @@ -359,7 +382,6 @@ public abstract class MockRestRequestMatchers { private static void assertValueCount(String name, MultiValueMap map, int count) { - List values = map.get(name); String message = "Expected query param <" + name + ">"; if (values == null) { @@ -371,7 +393,6 @@ public abstract class MockRestRequestMatchers { } private static void assertValueCount(String name, HttpHeaders headers, int count) { - List values = headers.get(name); String message = "Expected header <" + name + ">"; if (values == null) { diff --git a/spring-test/src/test/java/org/springframework/test/web/client/match/MockRestRequestMatchersTests.java b/spring-test/src/test/java/org/springframework/test/web/client/match/MockRestRequestMatchersTests.java index a571c1d0f5a..d4ec3d7ca02 100644 --- a/spring-test/src/test/java/org/springframework/test/web/client/match/MockRestRequestMatchersTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/client/match/MockRestRequestMatchersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -287,7 +287,6 @@ class MockRestRequestMatchersTests { .withMessageContaining("was \"bar\""); } - @Test void queryParamListMissing() { assertThatAssertionError() @@ -353,6 +352,34 @@ class MockRestRequestMatchersTests { "but: was <[bar, baz]>"); } + @Test // gh-34703 + void queryParamCount() throws Exception { + this.request.setURI(URI.create("http://www.foo.example/a")); + MockRestRequestMatchers.queryParamCount(0).match(this.request); + + this.request.setURI(URI.create("http://www.foo.example/a?")); + MockRestRequestMatchers.queryParamCount(0).match(this.request); + + this.request.setURI(URI.create("http://www.foo.example/a?foo=1")); + MockRestRequestMatchers.queryParamCount(1).match(this.request); + + this.request.setURI(URI.create("http://www.foo.example/a?foo=1&foo=2")); + MockRestRequestMatchers.queryParamCount(1).match(this.request); + + this.request.setURI(URI.create("http://www.foo.example/a?foo=1&baz=2")); + MockRestRequestMatchers.queryParamCount(2).match(this.request); + } + + @Test // gh-34703 + void queryParamCountMismatch() { + this.request.setURI(URI.create("http://www.foo.example/a?foo=1&baz=2")); + + assertThatAssertionError() + .isThrownBy(() -> MockRestRequestMatchers.queryParamCount(1).match(this.request)) + .withMessage("Expected 1 query parameter(s) but found 2: [foo, baz]"); + } + + private static ThrowableTypeAssert assertThatAssertionError() { return assertThatExceptionOfType(AssertionError.class); }