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
This commit is contained in:
Phillip Webb 2025-09-30 15:59:03 -07:00
parent 3cced515ce
commit 2a521ce4e9
23 changed files with 242 additions and 496 deletions

View File

@ -59,7 +59,7 @@ import org.springframework.web.context.WebApplicationContext;
* <li>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.</li>
* random} port when {@code spring-boot-web-server} is on the classpath.</li>
* </ul>
*
* @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),

View File

@ -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"))

View File

@ -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"))

View File

@ -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.

View File

@ -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")

View File

@ -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")

View File

@ -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")

View File

@ -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")

View File

@ -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"))

View File

@ -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")

View File

@ -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")
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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<TestRestTemplate>(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<Foo>(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<Foo>(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<Foo>(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<Foo>(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<Foo>(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<Foo>(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<Foo>(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<Foo>(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<Foo>(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<Foo>(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<Foo>(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<Foo>(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<Foo>(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<Foo>(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<Foo>(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<Foo>(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<Foo>(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<Foo>(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<HttpEntity<Foo>>()
val var1 = "var1"
val var2 = "var2"
template.exchange<List<Foo>>(url, method, entity, var1, var2)
verify(exactly = 1) { template.exchange(url, method, entity,
object : ParameterizedTypeReference<List<Foo>>() {}, 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<HttpEntity<Foo>>()
val vars = mapOf(Pair("key1", "value1"), Pair("key2", "value2"))
template.exchange<List<Foo>>(url, method, entity, vars)
verify(exactly = 1) { template.exchange(url, method, entity,
object : ParameterizedTypeReference<List<Foo>>() {}, vars) }
}
@Test
fun `exchange with reified type parameters, String, HttpMethod, HttpEntity`() {
val url = "https://spring.io"
val method = HttpMethod.GET
val entity = mockk<HttpEntity<Foo>>()
template.exchange<List<Foo>>(url, method, entity)
verify(exactly = 1) { template.exchange(url, method, entity,
object : ParameterizedTypeReference<List<Foo>>() {}) }
}
@Test
fun `exchange with reified type parameters and HttpEntity`() {
val entity = mockk<RequestEntity<Foo>>()
template.exchange<List<Foo>>(entity)
verify(exactly = 1) { template.exchange(entity,
object : ParameterizedTypeReference<List<Foo>>() {}) }
}
@Test
fun `exchange with reified type parameters, String and HttpMethod`() {
val url = "https://spring.io"
val method = HttpMethod.GET
template.exchange<List<Foo>>(url, method)
verify(exactly = 1) { template.exchange(url, method, null,
object : ParameterizedTypeReference<List<Foo>>() {}) }
}
@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<Class<*>>(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
}

View File

@ -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<? extends ApplicationEvent> 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);
}
}
}

View File

@ -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<ContextConfigurationAttributes> 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();
}
}
}

View File

@ -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

View File

@ -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<String, Object> 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}\"");
}

View File

@ -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")))

View File

@ -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",

View File

@ -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"

View File

@ -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")

View File

@ -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")