From 2a521ce4e91e4aff3fe43bf4ed77b48558635b3f Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 30 Sep 2025 15:59:03 -0700 Subject: [PATCH] Remove `spring-boot-web-server-test` module Remove the `spring-boot-web-server-test` module, adding `SpringBootTestRandomPortContextCustomizerFactory` to `spring-boot-web-server` as a replacement for `SpringBootTestRandomPortApplicationListener`. See gh-46356 See gh-47322 --- .../boot/test/context/SpringBootTest.java | 6 +- .../spring-boot-actuator-docs/build.gradle | 1 - documentation/spring-boot-docs/build.gradle | 1 - .../reference/pages/testing/test-modules.adoc | 3 - .../build.gradle | 1 - module/spring-boot-devtools/build.gradle | 1 - module/spring-boot-mustache/build.gradle | 1 - module/spring-boot-restdocs/build.gradle | 1 - .../build.gradle | 1 - module/spring-boot-security/build.gradle | 2 - .../spring-boot-web-server-test/build.gradle | 57 ---- ...estRandomPortEnvironmentPostProcessor.java | 113 -------- .../boot/web/server/test/package-info.java | 23 -- .../client/TestRestTemplateExtensionsTests.kt | 265 ------------------ ...BootTestRandomPortApplicationListener.java | 155 ++++++++++ ...estRandomPortContextCustomizerFactory.java | 62 ++++ .../main/resources/META-INF/spring.factories | 4 + ...estRandomPortApplicationListenerTests.java | 36 +-- module/spring-boot-webmvc/build.gradle | 1 - .../spring-boot-dependencies/build.gradle | 1 - settings.gradle | 1 - .../build.gradle | 1 - starter/spring-boot-starter-test/build.gradle | 1 - 23 files changed, 242 insertions(+), 496 deletions(-) delete mode 100644 module/spring-boot-web-server-test/build.gradle delete mode 100644 module/spring-boot-web-server-test/src/main/java/org/springframework/boot/web/server/test/SpringBootTestRandomPortEnvironmentPostProcessor.java delete mode 100644 module/spring-boot-web-server-test/src/main/java/org/springframework/boot/web/server/test/package-info.java delete mode 100644 module/spring-boot-web-server-test/src/test/kotlin/org/springframework/boot/web/server/test/client/TestRestTemplateExtensionsTests.kt create mode 100644 module/spring-boot-web-server/src/main/java/org/springframework/boot/web/server/context/SpringBootTestRandomPortApplicationListener.java create mode 100644 module/spring-boot-web-server/src/main/java/org/springframework/boot/web/server/context/SpringBootTestRandomPortContextCustomizerFactory.java rename integration-test/spring-boot-test-integration-tests/src/test/java/org/springframework/boot/web/server/test/SpringBootTestRandomPortEnvironmentPostProcessorTests.java => module/spring-boot-web-server/src/test/java/org/springframework/boot/web/server/context/SpringBootTestRandomPortApplicationListenerTests.java (86%) diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTest.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTest.java index 9c20acadb70..70e5d66f067 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTest.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTest.java @@ -59,7 +59,7 @@ import org.springframework.web.context.WebApplicationContext; *
  • Provides support for different {@link #webEnvironment() webEnvironment} modes, * including the ability to start a fully running web server listening on a * {@link WebEnvironment#DEFINED_PORT defined} or {@link WebEnvironment#RANDOM_PORT - * random} port when {@code spring-boot-web-server-test} is on the classpath.
  • + * random} port when {@code spring-boot-web-server} is on the classpath. * * * @author Phillip Webb @@ -144,7 +144,7 @@ public @interface SpringBootTest { * Creates a web application context (reactive or servlet based) and sets a * {@code server.port=0} {@link Environment} property (which usually triggers * listening on a random port). Requires a dependency on - * {@code spring-boot-web-server-test}. Often used in conjunction with a + * {@code spring-boot-web-server}. Often used in conjunction with a * {@code @LocalServerPort} injected field on the test. */ RANDOM_PORT(true), @@ -152,7 +152,7 @@ public @interface SpringBootTest { /** * Creates a web application context (reactive or servlet based) without defining * any {@code server.port=0} {@link Environment} property. Requires a dependency - * on {@code spring-boot-web-server-test}. + * on {@code spring-boot-web-server}. */ DEFINED_PORT(true), diff --git a/documentation/spring-boot-actuator-docs/build.gradle b/documentation/spring-boot-actuator-docs/build.gradle index 081288cf3de..5d7fb2c97a2 100644 --- a/documentation/spring-boot-actuator-docs/build.gradle +++ b/documentation/spring-boot-actuator-docs/build.gradle @@ -37,7 +37,6 @@ dependencies { testImplementation(project(":module:spring-boot-reactor-netty")) testImplementation(project(":module:spring-boot-session")) testImplementation(project(":module:spring-boot-tomcat")) - testImplementation(project(":module:spring-boot-web-server-test")) testImplementation(project(":module:spring-boot-webflux")) testImplementation(project(":module:spring-boot-webmvc")) testImplementation(project(":test-support:spring-boot-test-support")) diff --git a/documentation/spring-boot-docs/build.gradle b/documentation/spring-boot-docs/build.gradle index 5622fc671e9..173bf28d381 100644 --- a/documentation/spring-boot-docs/build.gradle +++ b/documentation/spring-boot-docs/build.gradle @@ -125,7 +125,6 @@ dependencies { implementation(project(path: ":module:spring-boot-restclient-test")) implementation(project(path: ":module:spring-boot-security")) implementation(project(path: ":module:spring-boot-tomcat")) - implementation(project(path: ":module:spring-boot-web-server-test")) implementation(project(path: ":module:spring-boot-webclient")) implementation(project(path: ":module:spring-boot-webflux")) implementation(project(path: ":module:spring-boot-webflux-test")) diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/test-modules.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/test-modules.adoc index 24ea7f0f916..f1664260821 100644 --- a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/test-modules.adoc +++ b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/test-modules.adoc @@ -64,9 +64,6 @@ Spring Boot offers several focused, feature-specific `-test` modules: |`spring-boot-security-test` |Testing applications that use Spring Security. -|`spring-boot-web-server-test` -|Testing applications that use an embedded web server. - |`spring-boot-webflux-test` |Testing applications that use Spring WebFlux. Provides the `@WebFluxTest` test slice. diff --git a/integration-test/spring-boot-test-integration-tests/build.gradle b/integration-test/spring-boot-test-integration-tests/build.gradle index 5155e16be60..263b9a6f976 100644 --- a/integration-test/spring-boot-test-integration-tests/build.gradle +++ b/integration-test/spring-boot-test-integration-tests/build.gradle @@ -29,7 +29,6 @@ dependencies { testImplementation(project(":module:spring-boot-restclient-test")) testImplementation(project(":module:spring-boot-tomcat")) testImplementation(project(":module:spring-boot-web-server")) - testImplementation(project(":module:spring-boot-web-server-test")) testImplementation("io.projectreactor.netty:reactor-netty-http") testImplementation("org.springframework:spring-webmvc") testImplementation("org.springframework:spring-webflux") diff --git a/module/spring-boot-devtools/build.gradle b/module/spring-boot-devtools/build.gradle index 3bf620e23d0..3a707dfd978 100644 --- a/module/spring-boot-devtools/build.gradle +++ b/module/spring-boot-devtools/build.gradle @@ -56,7 +56,6 @@ dependencies { intTestImplementation(project(":core:spring-boot-test")) intTestImplementation(project(":module:spring-boot-restclient")) intTestImplementation(project(":module:spring-boot-restclient-test")) - intTestImplementation(project(":module:spring-boot-web-server-test")) intTestImplementation(project(":test-support:spring-boot-test-support")) intTestImplementation("org.apache.httpcomponents.client5:httpclient5") intTestImplementation("net.bytebuddy:byte-buddy") diff --git a/module/spring-boot-mustache/build.gradle b/module/spring-boot-mustache/build.gradle index ce67c600e7b..32730a38abd 100644 --- a/module/spring-boot-mustache/build.gradle +++ b/module/spring-boot-mustache/build.gradle @@ -42,7 +42,6 @@ dependencies { testImplementation(project(":module:spring-boot-tomcat")) testImplementation(project(":module:spring-boot-webflux-test")) testImplementation(project(":module:spring-boot-webmvc-test")) - testImplementation(project(":module:spring-boot-web-server-test")) testImplementation(project(":test-support:spring-boot-test-support")) testImplementation(testFixtures(project(":core:spring-boot-autoconfigure"))) testImplementation("io.projectreactor:reactor-test") diff --git a/module/spring-boot-restdocs/build.gradle b/module/spring-boot-restdocs/build.gradle index 46e6770b11e..ab3ac77f591 100644 --- a/module/spring-boot-restdocs/build.gradle +++ b/module/spring-boot-restdocs/build.gradle @@ -30,7 +30,6 @@ dependencies { optional(project(":core:spring-boot-autoconfigure")) optional(project(":core:spring-boot-test-autoconfigure")) optional(project(":module:spring-boot-webmvc-test")) - optional(project(":module:spring-boot-web-server-test")) optional("org.springframework.restdocs:spring-restdocs-mockmvc") optional("org.springframework.restdocs:spring-restdocs-restassured") optional("org.springframework.restdocs:spring-restdocs-webtestclient") diff --git a/module/spring-boot-security-oauth2-client/build.gradle b/module/spring-boot-security-oauth2-client/build.gradle index 953f58861bc..ba2625c2ddf 100644 --- a/module/spring-boot-security-oauth2-client/build.gradle +++ b/module/spring-boot-security-oauth2-client/build.gradle @@ -35,7 +35,6 @@ dependencies { optional(project(":module:spring-boot-reactor")) optional(project(":module:spring-boot-webflux-test")) optional(project(":module:spring-boot-webmvc-test")) - optional(project(":module:spring-boot-web-server-test")) testImplementation(project(":core:spring-boot-test-autoconfigure")) testImplementation(project(":module:spring-boot-tomcat")) diff --git a/module/spring-boot-security/build.gradle b/module/spring-boot-security/build.gradle index 65444283aad..7adb336db0d 100644 --- a/module/spring-boot-security/build.gradle +++ b/module/spring-boot-security/build.gradle @@ -40,7 +40,6 @@ dependencies { optional(project(":module:spring-boot-webflux")) optional(project(":module:spring-boot-webmvc")) optional(project(":module:spring-boot-web-server")) - optional(project(":module:spring-boot-web-server-test")) optional("jakarta.servlet:jakarta.servlet-api") optional("org.springframework:spring-messaging") optional("org.springframework:spring-webflux") @@ -56,7 +55,6 @@ dependencies { testImplementation(project(":module:spring-boot-restclient")) testImplementation(project(":module:spring-boot-rsocket")) testImplementation(project(":module:spring-boot-tomcat")) - testImplementation(project(":module:spring-boot-web-server-test")) testImplementation(project(":test-support:spring-boot-test-support")) testImplementation(testFixtures(project(":core:spring-boot-autoconfigure"))) testImplementation("com.squareup.okhttp3:mockwebserver") diff --git a/module/spring-boot-web-server-test/build.gradle b/module/spring-boot-web-server-test/build.gradle deleted file mode 100644 index cf7b52949e1..00000000000 --- a/module/spring-boot-web-server-test/build.gradle +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -plugins { - id "dev.adamko.dokkatoo-html" - id "java-library" - id "org.springframework.boot.deployed" - id "org.springframework.boot.optional-dependencies" - id "org.springframework.boot.test-auto-configuration" -} - -description = "Spring Boot Web Server Test" - -dependencies { - api(project(":core:spring-boot")) - api(project(":core:spring-boot-test-autoconfigure")) - - optional(project(":core:spring-boot-autoconfigure")) - optional(project(":module:spring-boot-http-codec")) - optional(project(":module:spring-boot-restclient")) - optional(project(":module:spring-boot-web-server")) - optional(project(":module:spring-boot-webclient")) - optional("jakarta.servlet:jakarta.servlet-api") - optional("org.htmlunit:htmlunit") - optional("org.jetbrains.kotlin:kotlin-reflect") - optional("org.jetbrains.kotlin:kotlin-stdlib") - optional("org.seleniumhq.selenium:htmlunit3-driver") { - exclude(group: "com.sun.activation", module: "jakarta.activation") - } - optional("org.seleniumhq.selenium:selenium-api") - optional("org.springframework:spring-test") - optional("org.springframework:spring-web") - optional("org.springframework:spring-webflux") - optional("org.springframework.security:spring-security-test") - - testImplementation(project(":core:spring-boot-test")) - testImplementation(project(":module:spring-boot-tomcat")) - testImplementation(project(":test-support:spring-boot-test-support")) - testImplementation("io.mockk:mockk") - testImplementation("io.projectreactor.netty:reactor-netty-http") - testImplementation("org.springframework:spring-webmvc") - - testRuntimeOnly("ch.qos.logback:logback-classic") -} diff --git a/module/spring-boot-web-server-test/src/main/java/org/springframework/boot/web/server/test/SpringBootTestRandomPortEnvironmentPostProcessor.java b/module/spring-boot-web-server-test/src/main/java/org/springframework/boot/web/server/test/SpringBootTestRandomPortEnvironmentPostProcessor.java deleted file mode 100644 index ed1dd51ce2e..00000000000 --- a/module/spring-boot-web-server-test/src/main/java/org/springframework/boot/web/server/test/SpringBootTestRandomPortEnvironmentPostProcessor.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.web.server.test; - -import java.util.Objects; - -import org.jspecify.annotations.Nullable; - -import org.springframework.boot.EnvironmentPostProcessor; -import org.springframework.boot.SpringApplication; -import org.springframework.core.convert.ConversionFailedException; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.MapPropertySource; -import org.springframework.core.env.PropertySource; -import org.springframework.lang.Contract; -import org.springframework.test.context.support.TestPropertySourceUtils; -import org.springframework.util.ClassUtils; - -/** - * {@link EnvironmentPostProcessor} implementation to start the management context on a - * random port if the main server's port is 0 and the management context is expected on a - * different port. - * - * @author Madhura Bhave - * @author Andy Wilkinson - */ -class SpringBootTestRandomPortEnvironmentPostProcessor implements EnvironmentPostProcessor { - - private static final String MANAGEMENT_PORT_PROPERTY = "management.server.port"; - - private static final String SERVER_PORT_PROPERTY = "server.port"; - - @Override - public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { - MapPropertySource source = (MapPropertySource) environment.getPropertySources() - .get(TestPropertySourceUtils.INLINED_PROPERTIES_PROPERTY_SOURCE_NAME); - if (source == null || isTestServerPortFixed(source, environment) || isTestManagementPortConfigured(source)) { - return; - } - Integer managementPort = getPropertyAsInteger(environment, MANAGEMENT_PORT_PROPERTY, null); - if (managementPort == null || managementPort.equals(-1) || managementPort.equals(0)) { - return; - } - Integer serverPort = getPropertyAsInteger(environment, SERVER_PORT_PROPERTY, 8080); - if (!managementPort.equals(serverPort)) { - source.getSource().put(MANAGEMENT_PORT_PROPERTY, "0"); - } - else { - source.getSource().put(MANAGEMENT_PORT_PROPERTY, ""); - } - } - - private boolean isTestServerPortFixed(MapPropertySource source, ConfigurableEnvironment environment) { - return !Integer.valueOf(0).equals(getPropertyAsInteger(source, SERVER_PORT_PROPERTY, environment)); - } - - private boolean isTestManagementPortConfigured(PropertySource source) { - return source.getProperty(MANAGEMENT_PORT_PROPERTY) != null; - } - - @Contract("_, _, !null -> !null") - private @Nullable Integer getPropertyAsInteger(ConfigurableEnvironment environment, String property, - @Nullable Integer defaultValue) { - return environment.getPropertySources() - .stream() - .filter((source) -> !source.getName() - .equals(TestPropertySourceUtils.INLINED_PROPERTIES_PROPERTY_SOURCE_NAME)) - .map((source) -> getPropertyAsInteger(source, property, environment)) - .filter(Objects::nonNull) - .findFirst() - .orElse(defaultValue); - } - - private @Nullable Integer getPropertyAsInteger(PropertySource source, String property, - ConfigurableEnvironment environment) { - Object value = source.getProperty(property); - if (value == null) { - return null; - } - if (ClassUtils.isAssignableValue(Integer.class, value)) { - return (Integer) value; - } - try { - return environment.getConversionService().convert(value, Integer.class); - } - catch (ConversionFailedException ex) { - if (value instanceof String string) { - return getResolvedValueIfPossible(environment, string); - } - throw ex; - } - } - - private @Nullable Integer getResolvedValueIfPossible(ConfigurableEnvironment environment, String value) { - String resolvedValue = environment.resolveRequiredPlaceholders(value); - return environment.getConversionService().convert(resolvedValue, Integer.class); - } - -} diff --git a/module/spring-boot-web-server-test/src/main/java/org/springframework/boot/web/server/test/package-info.java b/module/spring-boot-web-server-test/src/main/java/org/springframework/boot/web/server/test/package-info.java deleted file mode 100644 index 9db72f1ce5e..00000000000 --- a/module/spring-boot-web-server-test/src/main/java/org/springframework/boot/web/server/test/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Web server test utilities and support classes. - */ -@NullMarked -package org.springframework.boot.web.server.test; - -import org.jspecify.annotations.NullMarked; diff --git a/module/spring-boot-web-server-test/src/test/kotlin/org/springframework/boot/web/server/test/client/TestRestTemplateExtensionsTests.kt b/module/spring-boot-web-server-test/src/test/kotlin/org/springframework/boot/web/server/test/client/TestRestTemplateExtensionsTests.kt deleted file mode 100644 index f766585c9c8..00000000000 --- a/module/spring-boot-web-server-test/src/test/kotlin/org/springframework/boot/web/server/test/client/TestRestTemplateExtensionsTests.kt +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.web.server.test.client - -import io.mockk.mockk -import io.mockk.verify -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test -import org.springframework.core.ParameterizedTypeReference -import org.springframework.http.HttpEntity -import org.springframework.http.HttpMethod -import org.springframework.http.RequestEntity -import org.springframework.util.ReflectionUtils -import org.springframework.web.client.RestOperations -import java.net.URI -import kotlin.reflect.full.createType -import kotlin.reflect.jvm.kotlinFunction - -/** - * Mock object based tests for [TestRestTemplate] Kotlin extensions - * - * @author Sebastien Deleuze - */ -class TestRestTemplateExtensionsTests { - - val template = mockk(relaxed = true) - - @Test - fun `getForObject with reified type parameters, String and varargs`() { - val url = "https://spring.io" - val var1 = "var1" - val var2 = "var2" - template.getForObject(url, var1, var2) - template.restTemplate - verify(exactly = 1) { template.getForObject(url, Foo::class.java, var1, var2) } - } - - @Test - fun `getForObject with reified type parameters, String and Map`() { - val url = "https://spring.io" - val vars = mapOf(Pair("key1", "value1"), Pair("key2", "value2")) - template.getForObject(url, vars) - verify(exactly = 1) { template.getForObject(url, Foo::class.java, vars) } - } - - @Test - fun `getForObject with reified type parameters and URI`() { - val url = URI("https://spring.io") - template.getForObject(url) - verify(exactly = 1) { template.getForObject(url, Foo::class.java) } - } - - @Test - fun `getForEntity with reified type parameters and URI`() { - val url = URI("https://spring.io") - template.getForEntity(url) - verify(exactly = 1) { template.getForEntity(url, Foo::class.java) } - } - - @Test - fun `getForEntity with reified type parameters, String and varargs`() { - val url = "https://spring.io" - val var1 = "var1" - val var2 = "var2" - template.getForEntity(url, var1, var2) - verify(exactly = 1) { template.getForEntity(url, Foo::class.java, var1, var2) } - } - - @Test - fun `getForEntity with reified type parameters, String and Map`() { - val url = "https://spring.io" - val vars = mapOf(Pair("key1", "value1"), Pair("key2", "value2")) - template.getForEntity(url, vars) - verify(exactly = 1) { template.getForEntity(url, Foo::class.java, vars) } - } - - @Test - fun `patchForObject with reified type parameters, String, Any and varargs`() { - val url = "https://spring.io" - val body: Any = "body" - val var1 = "var1" - val var2 = "var2" - template.patchForObject(url, body, var1, var2) - verify(exactly = 1) { template.patchForObject(url, body, Foo::class.java, var1, var2) } - } - - @Test - fun `patchForObject with reified type parameters, String, Any and Map`() { - val url = "https://spring.io" - val body: Any = "body" - val vars = mapOf(Pair("key1", "value1"), Pair("key2", "value2")) - template.patchForObject(url, body, vars) - verify(exactly = 1) { template.patchForObject(url, body, Foo::class.java, vars) } - } - - @Test - fun `patchForObject with reified type parameters, String and Any`() { - val url = "https://spring.io" - val body: Any = "body" - template.patchForObject(url, body) - verify(exactly = 1) { template.patchForObject(url, body, Foo::class.java) } - } - - @Test - fun `patchForObject with reified type parameters`() { - val url = "https://spring.io" - template.patchForObject(url) - verify(exactly = 1) { template.patchForObject(url, null, Foo::class.java) } - } - - @Test - fun `postForObject with reified type parameters, String, Any and varargs`() { - val url = "https://spring.io" - val body: Any = "body" - val var1 = "var1" - val var2 = "var2" - template.postForObject(url, body, var1, var2) - verify(exactly = 1) { template.postForObject(url, body, Foo::class.java, var1, var2) } - } - - @Test - fun `postForObject with reified type parameters, String, Any and Map`() { - val url = "https://spring.io" - val body: Any = "body" - val vars = mapOf(Pair("key1", "value1"), Pair("key2", "value2")) - template.postForObject(url, body, vars) - verify(exactly = 1) { template.postForObject(url, body, Foo::class.java, vars) } - } - - @Test - fun `postForObject with reified type parameters, String and Any`() { - val url = "https://spring.io" - val body: Any = "body" - template.postForObject(url, body) - verify(exactly = 1) { template.postForObject(url, body, Foo::class.java) } - } - - @Test - fun `postForObject with reified type parameters`() { - val url = "https://spring.io" - template.postForObject(url) - verify(exactly = 1) { template.postForObject(url, null, Foo::class.java) } - } - - @Test - fun `postForEntity with reified type parameters, String, Any and varargs`() { - val url = "https://spring.io" - val body: Any = "body" - val var1 = "var1" - val var2 = "var2" - template.postForEntity(url, body, var1, var2) - verify(exactly = 1) { template.postForEntity(url, body, Foo::class.java, var1, var2) } - } - - @Test - fun `postForEntity with reified type parameters, String, Any and Map`() { - val url = "https://spring.io" - val body: Any = "body" - val vars = mapOf(Pair("key1", "value1"), Pair("key2", "value2")) - template.postForEntity(url, body, vars) - verify(exactly = 1) { template.postForEntity(url, body, Foo::class.java, vars) } - } - - @Test - fun `postForEntity with reified type parameters, String and Any`() { - val url = "https://spring.io" - val body: Any = "body" - template.postForEntity(url, body) - verify(exactly = 1) { template.postForEntity(url, body, Foo::class.java) } - } - - @Test - fun `postForEntity with reified type parameters`() { - val url = "https://spring.io" - template.postForEntity(url) - verify(exactly = 1) { template.postForEntity(url, null, Foo::class.java) } - } - - @Test - fun `exchange with reified type parameters, String, HttpMethod, HttpEntity and varargs`() { - val url = "https://spring.io" - val method = HttpMethod.GET - val entity = mockk>() - val var1 = "var1" - val var2 = "var2" - template.exchange>(url, method, entity, var1, var2) - verify(exactly = 1) { template.exchange(url, method, entity, - object : ParameterizedTypeReference>() {}, var1, var2) } - } - - @Test - fun `exchange with reified type parameters, String, HttpMethod, HttpEntity and Map`() { - val url = "https://spring.io" - val method = HttpMethod.GET - val entity = mockk>() - val vars = mapOf(Pair("key1", "value1"), Pair("key2", "value2")) - template.exchange>(url, method, entity, vars) - verify(exactly = 1) { template.exchange(url, method, entity, - object : ParameterizedTypeReference>() {}, vars) } - } - - @Test - fun `exchange with reified type parameters, String, HttpMethod, HttpEntity`() { - val url = "https://spring.io" - val method = HttpMethod.GET - val entity = mockk>() - template.exchange>(url, method, entity) - verify(exactly = 1) { template.exchange(url, method, entity, - object : ParameterizedTypeReference>() {}) } - } - - @Test - fun `exchange with reified type parameters and HttpEntity`() { - val entity = mockk>() - template.exchange>(entity) - verify(exactly = 1) { template.exchange(entity, - object : ParameterizedTypeReference>() {}) } - } - - @Test - fun `exchange with reified type parameters, String and HttpMethod`() { - val url = "https://spring.io" - val method = HttpMethod.GET - template.exchange>(url, method) - verify(exactly = 1) { template.exchange(url, method, null, - object : ParameterizedTypeReference>() {}) } - } - - @Test - fun `RestOperations are available`() { - val extensions = Class.forName( - "org.springframework.boot.web.server.test.client.TestRestTemplateExtensionsKt") - ReflectionUtils.doWithMethods(RestOperations::class.java) { method -> - arrayOf(ParameterizedTypeReference::class, Class::class).forEach { kClass -> - if (method.parameterTypes.contains(kClass.java)) { - val parameters = mutableListOf>(TestRestTemplate::class.java) - .apply { addAll(method.parameterTypes.filter { it != kClass.java }) } - val f = extensions.getDeclaredMethod(method.name, - *parameters.toTypedArray()).kotlinFunction!! - assertThat(f.typeParameters.size).isEqualTo(1) - assertThat(listOf(Any::class.createType())) - .isEqualTo(f.typeParameters[0].upperBounds) - } - } - } - } - - class Foo - -} - diff --git a/module/spring-boot-web-server/src/main/java/org/springframework/boot/web/server/context/SpringBootTestRandomPortApplicationListener.java b/module/spring-boot-web-server/src/main/java/org/springframework/boot/web/server/context/SpringBootTestRandomPortApplicationListener.java new file mode 100644 index 00000000000..00bf9b01301 --- /dev/null +++ b/module/spring-boot-web-server/src/main/java/org/springframework/boot/web/server/context/SpringBootTestRandomPortApplicationListener.java @@ -0,0 +1,155 @@ +/* + * Copyright 2012-present 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.server.context; + +import java.util.Objects; + +import org.jspecify.annotations.Nullable; + +import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.event.SmartApplicationListener; +import org.springframework.core.Ordered; +import org.springframework.core.convert.ConversionFailedException; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertyResolver; +import org.springframework.core.env.PropertySource; +import org.springframework.core.env.PropertySources; +import org.springframework.lang.Contract; +import org.springframework.test.context.support.TestPropertySourceUtils; +import org.springframework.util.ClassUtils; + +/** + * {@link SmartApplicationListener} for tests that starts the management context on a + * random port if the main server's port is 0 and the management context is expected on a + * different port. + * + * @author Madhura Bhave + * @author Andy Wilkinson + * @author Phillip Webb + */ +class SpringBootTestRandomPortApplicationListener implements SmartApplicationListener, Ordered { + + private static final String TEST_SOURCE_NAME = TestPropertySourceUtils.INLINED_PROPERTIES_PROPERTY_SOURCE_NAME; + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } + + @Override + public boolean supportsEventType(Class eventType) { + return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventType); + } + + @Override + public void onApplicationEvent(ApplicationEvent event) { + if (event instanceof ApplicationEnvironmentPreparedEvent environmentPreparedEvent) { + postProcessEnvironment(environmentPreparedEvent.getEnvironment()); + } + } + + void postProcessEnvironment(ConfigurableEnvironment environment) { + MapPropertySource testSource = (MapPropertySource) environment.getPropertySources().get(TEST_SOURCE_NAME); + if (testSource != null) { + MutablePropertySources nonTestSources = new MutablePropertySources(environment.getPropertySources()); + nonTestSources.remove(TEST_SOURCE_NAME); + Ports ports = new Ports(environment, environment.getConversionService()); + String value = getManagementServerPortPropertyValue(testSource, nonTestSources, ports); + if (value != null) { + testSource.getSource().put(Port.MANAGEMENT.property(), value); + } + } + } + + private @Nullable String getManagementServerPortPropertyValue(MapPropertySource testSource, + MutablePropertySources nonTestSources, Ports ports) { + if (ports.isFixed(testSource, Port.SERVER) || ports.isConfigured(testSource, Port.MANAGEMENT)) { + return null; + } + Integer managementPort = ports.get(nonTestSources, Port.MANAGEMENT, null); + if (managementPort == null || managementPort.equals(-1) || managementPort.equals(0)) { + return null; + } + Integer serverPort = ports.get(nonTestSources, Port.SERVER, 8080); + return (!managementPort.equals(serverPort)) ? "0" : ""; + } + + private enum Port { + + SERVER("server.port"), MANAGEMENT("management.server.port"); + + private final String property; + + Port(String property) { + this.property = property; + } + + String property() { + return this.property; + } + + } + + private record Ports(PropertyResolver resolver, ConversionService conversionService) { + + private static final Integer ZERO = Integer.valueOf(0); + + boolean isFixed(MapPropertySource source, Port port) { + return !ZERO.equals(get(source, port, null)); + } + + boolean isConfigured(PropertySource source, Port port) { + return source.getProperty(port.property()) != null; + } + + @Contract("_, _, !null -> !null") + @Nullable Integer get(PropertySources sources, Port port, @Nullable Integer defaultValue) { + return sources.stream() + .map((source) -> get(source, port, defaultValue)) + .filter(Objects::nonNull) + .findFirst() + .orElse(defaultValue); + } + + @Contract("_, _, !null -> !null") + @Nullable Integer get(PropertySource source, Port port, @Nullable Integer defaultValue) { + Object value = source.getProperty(port.property()); + if (value == null || ClassUtils.isAssignableValue(Integer.class, value)) { + return (Integer) value; + } + try { + return asInteger(value); + } + catch (ConversionFailedException ex) { + if (value instanceof String string) { + return asInteger(resolver().resolveRequiredPlaceholders(string)); + } + throw ex; + } + } + + private @Nullable Integer asInteger(@Nullable Object value) { + return conversionService().convert(value, Integer.class); + } + + } + +} diff --git a/module/spring-boot-web-server/src/main/java/org/springframework/boot/web/server/context/SpringBootTestRandomPortContextCustomizerFactory.java b/module/spring-boot-web-server/src/main/java/org/springframework/boot/web/server/context/SpringBootTestRandomPortContextCustomizerFactory.java new file mode 100644 index 00000000000..0c336e5e436 --- /dev/null +++ b/module/spring-boot-web-server/src/main/java/org/springframework/boot/web/server/context/SpringBootTestRandomPortContextCustomizerFactory.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012-present 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.web.server.context; + +import java.util.List; + +import org.jspecify.annotations.Nullable; + +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.test.context.ContextConfigurationAttributes; +import org.springframework.test.context.ContextCustomizer; +import org.springframework.test.context.ContextCustomizerFactory; +import org.springframework.test.context.MergedContextConfiguration; + +/** + * {@link ContextCustomizerFactory} apply + * {@link SpringBootTestRandomPortApplicationListener} to tests. + * + * @author Phillip Webb + */ +class SpringBootTestRandomPortContextCustomizerFactory implements ContextCustomizerFactory { + + @Override + public @Nullable ContextCustomizer createContextCustomizer(Class testClass, + List configAttributes) { + return new Customizer(); + } + + static class Customizer implements ContextCustomizer { + + @Override + public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) { + context.addApplicationListener(new SpringBootTestRandomPortApplicationListener()); + } + + @Override + public boolean equals(Object obj) { + return (obj != null) && (obj.getClass() == getClass()); + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } + + } + +} diff --git a/module/spring-boot-web-server/src/main/resources/META-INF/spring.factories b/module/spring-boot-web-server/src/main/resources/META-INF/spring.factories index 9531f3d70b1..31291c6e017 100644 --- a/module/spring-boot-web-server/src/main/resources/META-INF/spring.factories +++ b/module/spring-boot-web-server/src/main/resources/META-INF/spring.factories @@ -16,3 +16,7 @@ org.springframework.boot.web.server.context.MissingWebServerFactoryBeanFailureAn org.springframework.boot.test.http.server.BaseUrlProvider=\ org.springframework.boot.web.server.reactive.context.ReactiveWebServerApplicationContextBaseUrlProvider,\ org.springframework.boot.web.server.servlet.context.ServletWebServerApplicationContextBaseUrlProvider + +# Spring Test Context Customizer Factories +org.springframework.test.context.ContextCustomizerFactory=\ +org.springframework.boot.web.server.context.SpringBootTestRandomPortContextCustomizerFactory diff --git a/integration-test/spring-boot-test-integration-tests/src/test/java/org/springframework/boot/web/server/test/SpringBootTestRandomPortEnvironmentPostProcessorTests.java b/module/spring-boot-web-server/src/test/java/org/springframework/boot/web/server/context/SpringBootTestRandomPortApplicationListenerTests.java similarity index 86% rename from integration-test/spring-boot-test-integration-tests/src/test/java/org/springframework/boot/web/server/test/SpringBootTestRandomPortEnvironmentPostProcessorTests.java rename to module/spring-boot-web-server/src/test/java/org/springframework/boot/web/server/context/SpringBootTestRandomPortApplicationListenerTests.java index 91a21620034..944ee5ea2b9 100644 --- a/integration-test/spring-boot-test-integration-tests/src/test/java/org/springframework/boot/web/server/test/SpringBootTestRandomPortEnvironmentPostProcessorTests.java +++ b/module/spring-boot-web-server/src/test/java/org/springframework/boot/web/server/context/SpringBootTestRandomPortApplicationListenerTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.web.server.test; +package org.springframework.boot.web.server.context; import java.util.Collections; import java.util.HashMap; @@ -33,14 +33,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** - * Tests for {@link SpringBootTestRandomPortEnvironmentPostProcessor}. + * Tests for {@link SpringBootTestRandomPortApplicationListener}. * * @author Madhura Bhave * @author Andy Wilkinson */ -class SpringBootTestRandomPortEnvironmentPostProcessorTests { +class SpringBootTestRandomPortApplicationListenerTests { - private final SpringBootTestRandomPortEnvironmentPostProcessor postProcessor = new SpringBootTestRandomPortEnvironmentPostProcessor(); + private final SpringBootTestRandomPortApplicationListener listener = new SpringBootTestRandomPortApplicationListener(); private MockEnvironment environment; @@ -55,7 +55,7 @@ class SpringBootTestRandomPortEnvironmentPostProcessorTests { @Test void postProcessWhenServerAndManagementPortIsZeroInTestPropertySource() { addTestPropertySource("0", "0"); - this.postProcessor.postProcessEnvironment(this.environment, null); + this.listener.postProcessEnvironment(this.environment); assertThat(this.environment.getProperty("server.port")).isEqualTo("0"); assertThat(this.environment.getProperty("management.server.port")).isEqualTo("0"); } @@ -66,7 +66,7 @@ class SpringBootTestRandomPortEnvironmentPostProcessorTests { Map source = new HashMap<>(); source.put("management.server.port", "0"); this.propertySources.addLast(new MapPropertySource("other", source)); - this.postProcessor.postProcessEnvironment(this.environment, null); + this.listener.postProcessEnvironment(this.environment); assertThat(this.environment.getProperty("server.port")).isEqualTo("0"); assertThat(this.environment.getProperty("management.server.port")).isEqualTo("0"); } @@ -76,7 +76,7 @@ class SpringBootTestRandomPortEnvironmentPostProcessorTests { addTestPropertySource("8080", "8081"); this.environment.setProperty("server.port", "8080"); this.environment.setProperty("management.server.port", "8081"); - this.postProcessor.postProcessEnvironment(this.environment, null); + this.listener.postProcessEnvironment(this.environment); assertThat(this.environment.getProperty("server.port")).isEqualTo("8080"); assertThat(this.environment.getProperty("management.server.port")).isEqualTo("8081"); } @@ -84,7 +84,7 @@ class SpringBootTestRandomPortEnvironmentPostProcessorTests { @Test void postProcessWhenTestServerPortIsZeroAndTestManagementPortIsNotNull() { addTestPropertySource("0", "8080"); - this.postProcessor.postProcessEnvironment(this.environment, null); + this.listener.postProcessEnvironment(this.environment); assertThat(this.environment.getProperty("server.port")).isEqualTo("0"); assertThat(this.environment.getProperty("management.server.port")).isEqualTo("8080"); } @@ -92,7 +92,7 @@ class SpringBootTestRandomPortEnvironmentPostProcessorTests { @Test void postProcessWhenTestServerPortIsZeroAndManagementPortIsNull() { addTestPropertySource("0", null); - this.postProcessor.postProcessEnvironment(this.environment, null); + this.listener.postProcessEnvironment(this.environment); assertThat(this.environment.getProperty("server.port")).isEqualTo("0"); assertThat(this.environment.getProperty("management.server.port")).isNull(); } @@ -105,7 +105,7 @@ class SpringBootTestRandomPortEnvironmentPostProcessorTests { other.put("management.server.port", "8081"); MapPropertySource otherSource = new MapPropertySource("other", other); this.propertySources.addLast(otherSource); - this.postProcessor.postProcessEnvironment(this.environment, null); + this.listener.postProcessEnvironment(this.environment); assertThat(this.environment.getProperty("server.port")).isEqualTo("0"); assertThat(this.environment.getProperty("management.server.port")).isEmpty(); } @@ -117,7 +117,7 @@ class SpringBootTestRandomPortEnvironmentPostProcessorTests { addTestPropertySource("0", null); this.propertySources .addLast(new MapPropertySource("other", Collections.singletonMap("management.server.port", "8080"))); - this.postProcessor.postProcessEnvironment(this.environment, null); + this.listener.postProcessEnvironment(this.environment); assertThat(this.environment.getProperty("server.port")).isEqualTo("0"); assertThat(this.environment.getProperty("management.server.port")).isEmpty(); } @@ -127,7 +127,7 @@ class SpringBootTestRandomPortEnvironmentPostProcessorTests { addTestPropertySource("0", null); this.propertySources .addLast(new MapPropertySource("other", Collections.singletonMap("management.server.port", "8081"))); - this.postProcessor.postProcessEnvironment(this.environment, null); + this.listener.postProcessEnvironment(this.environment); assertThat(this.environment.getProperty("server.port")).isEqualTo("0"); assertThat(this.environment.getProperty("management.server.port")).isEqualTo("0"); } @@ -137,7 +137,7 @@ class SpringBootTestRandomPortEnvironmentPostProcessorTests { addTestPropertySource("0", null); this.propertySources .addLast(new MapPropertySource("other", Collections.singletonMap("management.server.port", "-1"))); - this.postProcessor.postProcessEnvironment(this.environment, null); + this.listener.postProcessEnvironment(this.environment); assertThat(this.environment.getProperty("server.port")).isEqualTo("0"); assertThat(this.environment.getProperty("management.server.port")).isEqualTo("-1"); } @@ -147,7 +147,7 @@ class SpringBootTestRandomPortEnvironmentPostProcessorTests { addTestPropertySource("0", null); this.propertySources .addLast(new MapPropertySource("other", Collections.singletonMap("management.server.port", 8081))); - this.postProcessor.postProcessEnvironment(this.environment, null); + this.listener.postProcessEnvironment(this.environment); assertThat(this.environment.getProperty("server.port")).isEqualTo("0"); assertThat(this.environment.getProperty("management.server.port")).isEqualTo("0"); } @@ -160,7 +160,7 @@ class SpringBootTestRandomPortEnvironmentPostProcessorTests { testPropertySource.getSource().put("port", "9090"); this.propertySources .addLast(new MapPropertySource("other", Collections.singletonMap("management.server.port", "${port}"))); - this.postProcessor.postProcessEnvironment(this.environment, null); + this.listener.postProcessEnvironment(this.environment); assertThat(this.environment.getProperty("server.port")).isEqualTo("0"); assertThat(this.environment.getProperty("management.server.port")).isEqualTo("0"); } @@ -171,7 +171,7 @@ class SpringBootTestRandomPortEnvironmentPostProcessorTests { this.propertySources .addLast(new MapPropertySource("other", Collections.singletonMap("management.server.port", "${port}"))); assertThatExceptionOfType(PlaceholderResolutionException.class) - .isThrownBy(() -> this.postProcessor.postProcessEnvironment(this.environment, null)) + .isThrownBy(() -> this.listener.postProcessEnvironment(this.environment)) .withMessage("Could not resolve placeholder 'port' in value \"${port}\""); } @@ -185,7 +185,7 @@ class SpringBootTestRandomPortEnvironmentPostProcessorTests { source.put("server.port", "${port}"); source.put("management.server.port", "9090"); this.propertySources.addLast(new MapPropertySource("other", source)); - this.postProcessor.postProcessEnvironment(this.environment, null); + this.listener.postProcessEnvironment(this.environment); assertThat(this.environment.getProperty("server.port")).isEqualTo("0"); assertThat(this.environment.getProperty("management.server.port")).isEqualTo("0"); } @@ -198,7 +198,7 @@ class SpringBootTestRandomPortEnvironmentPostProcessorTests { source.put("management.server.port", "9090"); this.propertySources.addLast(new MapPropertySource("other", source)); assertThatExceptionOfType(PlaceholderResolutionException.class) - .isThrownBy(() -> this.postProcessor.postProcessEnvironment(this.environment, null)) + .isThrownBy(() -> this.listener.postProcessEnvironment(this.environment)) .withMessage("Could not resolve placeholder 'port' in value \"${port}\""); } diff --git a/module/spring-boot-webmvc/build.gradle b/module/spring-boot-webmvc/build.gradle index 8196d4ea240..6d8f325ee29 100644 --- a/module/spring-boot-webmvc/build.gradle +++ b/module/spring-boot-webmvc/build.gradle @@ -55,7 +55,6 @@ dependencies { testImplementation(project(":module:spring-boot-restclient")) testImplementation(project(":module:spring-boot-restclient-test")) testImplementation(project(":module:spring-boot-tomcat")) - testImplementation(project(":module:spring-boot-web-server-test")) testImplementation(project(":test-support:spring-boot-test-support")) testImplementation(testFixtures(project(":module:spring-boot-actuator-autoconfigure"))) testImplementation(testFixtures(project(":module:spring-boot-web-server"))) diff --git a/platform/spring-boot-dependencies/build.gradle b/platform/spring-boot-dependencies/build.gradle index 5723210cecf..4c796d1df20 100644 --- a/platform/spring-boot-dependencies/build.gradle +++ b/platform/spring-boot-dependencies/build.gradle @@ -2179,7 +2179,6 @@ bom { "spring-boot-tx", "spring-boot-validation", "spring-boot-web-server", - "spring-boot-web-server-test", "spring-boot-webclient", "spring-boot-webflux", "spring-boot-webflux-test", diff --git a/settings.gradle b/settings.gradle index 8a47cc323cd..c57697e4dd1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -186,7 +186,6 @@ include "module:spring-boot-tomcat" include "module:spring-boot-tx" include "module:spring-boot-validation" include "module:spring-boot-web-server" // FIXME move to core? -include "module:spring-boot-web-server-test" // FIXME move to core? include "module:spring-boot-webclient" include "module:spring-boot-webflux" include "module:spring-boot-webflux-test" diff --git a/smoke-test/spring-boot-smoke-test-testng/build.gradle b/smoke-test/spring-boot-smoke-test-testng/build.gradle index 9fdd2cfd65e..5bd02ee9b69 100644 --- a/smoke-test/spring-boot-smoke-test-testng/build.gradle +++ b/smoke-test/spring-boot-smoke-test-testng/build.gradle @@ -29,7 +29,6 @@ dependencies { testImplementation(project(":module:spring-boot-restclient")) testImplementation(project(":module:spring-boot-restclient-test")) testImplementation(project(":core:spring-boot-test")) - testImplementation(project(":module:spring-boot-web-server-test")) testImplementation("org.assertj:assertj-core") testImplementation("org.springframework:spring-test") testImplementation("org.testng:testng:6.8.13") diff --git a/starter/spring-boot-starter-test/build.gradle b/starter/spring-boot-starter-test/build.gradle index bbabef93e3b..9b499469715 100644 --- a/starter/spring-boot-starter-test/build.gradle +++ b/starter/spring-boot-starter-test/build.gradle @@ -27,7 +27,6 @@ dependencies { api(project(":core:spring-boot-test-autoconfigure")) api(project(":module:spring-boot-restclient-test")) - api(project(":module:spring-boot-web-server-test")) api("com.jayway.jsonpath:json-path") api("jakarta.xml.bind:jakarta.xml.bind-api")