diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 1b7a59af5c..bec852c2ef 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -6491,6 +6491,10 @@ test method by default. However, as using this arrangement with either `RANDOM_P run in separate threads and, thus, in separate transactions. Any transaction initiated on the server does not roll back in this case. +NOTE: `@SpringBootTest` with `webEnvironment = WebEnvironment.RANDOM_PORT` will also +start the management server on a separate random port if your application uses a different +port for the management server. + [[boot-features-testing-spring-boot-applications-detecting-web-app-type]] diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/SpringBootTestRandomPortEnvironmentPostProcessor.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/SpringBootTestRandomPortEnvironmentPostProcessor.java new file mode 100644 index 0000000000..ce9f6ab91a --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/SpringBootTestRandomPortEnvironmentPostProcessor.java @@ -0,0 +1,77 @@ +/* + * Copyright 2012-2018 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 + * + * http://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.test.web; + +import java.util.Objects; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.env.EnvironmentPostProcessor; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.test.context.support.TestPropertySourceUtils; + +/** + * {@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 + * @since 2.1.0 + */ +public 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 (isTestServerPortRandom(source)) { + if (source.getProperty(MANAGEMENT_PORT_PROPERTY) == null) { + String managementPort = getPort(environment, MANAGEMENT_PORT_PROPERTY, + null); + String serverPort = getPort(environment, SERVER_PORT_PROPERTY, "8080"); + if (managementPort != null && !managementPort.equals("-1")) { + if (!managementPort.equals(serverPort)) { + source.getSource().put(MANAGEMENT_PORT_PROPERTY, "0"); + } + else { + source.getSource().put(MANAGEMENT_PORT_PROPERTY, ""); + } + } + } + } + + } + + private boolean isTestServerPortRandom(MapPropertySource source) { + return (source != null && "0".equals(source.getProperty(SERVER_PORT_PROPERTY))); + } + + private String getPort(ConfigurableEnvironment environment, String property, + String defaultValue) { + return environment.getPropertySources().stream() + .filter((source) -> !source.getName().equals( + TestPropertySourceUtils.INLINED_PROPERTIES_PROPERTY_SOURCE_NAME)) + .map((source) -> (String) source.getProperty(property)) + .filter(Objects::nonNull).findFirst().orElse(defaultValue); + } + +} diff --git a/spring-boot-project/spring-boot-test/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-test/src/main/resources/META-INF/spring.factories index dace6b7b70..5a75a417aa 100644 --- a/spring-boot-project/spring-boot-test/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-test/src/main/resources/META-INF/spring.factories @@ -11,3 +11,7 @@ org.springframework.boot.test.web.reactive.server.WebTestClientContextCustomizer org.springframework.test.context.TestExecutionListener=\ org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener,\ org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener + +# Environment Post Processors +org.springframework.boot.env.EnvironmentPostProcessor=\ +org.springframework.boot.test.web.SpringBootTestRandomPortEnvironmentPostProcessor diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/SpringBootTestRandomPortEnvironmentPostProcessorTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/SpringBootTestRandomPortEnvironmentPostProcessorTests.java new file mode 100644 index 0000000000..f439f53993 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/SpringBootTestRandomPortEnvironmentPostProcessorTests.java @@ -0,0 +1,149 @@ +/* + * Copyright 2012-2018 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 + * + * http://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.test.web; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; + +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.mock.env.MockEnvironment; +import org.springframework.test.context.support.TestPropertySourceUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SpringBootTestRandomPortEnvironmentPostProcessor}. + * + * @author Madhura Bhave + */ +public class SpringBootTestRandomPortEnvironmentPostProcessorTests { + + private SpringBootTestRandomPortEnvironmentPostProcessor postProcessor = new SpringBootTestRandomPortEnvironmentPostProcessor(); + + private MockEnvironment environment; + + private MutablePropertySources propertySources; + + @Before + public void setup() { + this.environment = new MockEnvironment(); + this.propertySources = this.environment.getPropertySources(); + } + + @Test + public void postProcessWhenServerAndManagementPortIsZeroInTestPropertySource() { + addTestPropertySource("0", "0"); + this.environment.setProperty("management.server.port", "0"); + this.postProcessor.postProcessEnvironment(this.environment, null); + assertThat(this.environment.getProperty("server.port")).isEqualTo("0"); + assertThat(this.environment.getProperty("management.server.port")).isEqualTo("0"); + } + + @Test + public void postProcessWhenTestServerAndTestManagementPortAreNonZero() { + addTestPropertySource("8080", "8081"); + this.environment.setProperty("server.port", "8080"); + this.environment.setProperty("management.server.port", "8081"); + this.postProcessor.postProcessEnvironment(this.environment, null); + assertThat(this.environment.getProperty("server.port")).isEqualTo("8080"); + assertThat(this.environment.getProperty("management.server.port")) + .isEqualTo("8081"); + } + + @Test + public void postProcessWhenTestServerPortIsZeroAndTestManagementPortIsNotNull() { + addTestPropertySource("0", "8080"); + this.postProcessor.postProcessEnvironment(this.environment, null); + assertThat(this.environment.getProperty("server.port")).isEqualTo("0"); + assertThat(this.environment.getProperty("management.server.port")) + .isEqualTo("8080"); + } + + @Test + public void postProcessWhenTestServerPortIsZeroAndManagementPortIsNull() { + addTestPropertySource("0", null); + this.postProcessor.postProcessEnvironment(this.environment, null); + assertThat(this.environment.getProperty("server.port")).isEqualTo("0"); + assertThat(this.environment.getProperty("management.server.port")) + .isEqualTo(null); + } + + @Test + public void postProcessWhenTestServerPortIsZeroAndManagementPortIsNotNullAndSameInProduction() { + addTestPropertySource("0", null); + Map other = new HashMap<>(); + other.put("server.port", "8081"); + other.put("management.server.port", "8081"); + MapPropertySource otherSource = new MapPropertySource("other", other); + this.propertySources.addLast(otherSource); + this.postProcessor.postProcessEnvironment(this.environment, null); + assertThat(this.environment.getProperty("server.port")).isEqualTo("0"); + assertThat(this.environment.getProperty("management.server.port")).isEqualTo(""); + } + + @Test + public void postProcessWhenTestServerPortIsZeroAndManagementPortIsNotNullAndDefaultSameInProduction() { + // mgmt port is 8080 which means its on the same port as main server since that is + // null in app properties + addTestPropertySource("0", null); + Map other = new HashMap<>(); + other.put("management.server.port", "8080"); + MapPropertySource otherSource = new MapPropertySource("other", other); + this.propertySources.addLast(otherSource); + this.postProcessor.postProcessEnvironment(this.environment, null); + assertThat(this.environment.getProperty("server.port")).isEqualTo("0"); + assertThat(this.environment.getProperty("management.server.port")).isEqualTo(""); + } + + @Test + public void postProcessWhenTestServerPortIsZeroAndManagementPortIsNotNullAndDifferentInProduction() { + addTestPropertySource("0", null); + Map other = new HashMap<>(); + other.put("management.server.port", "8081"); + MapPropertySource otherSource = new MapPropertySource("other", other); + this.propertySources.addLast(otherSource); + this.postProcessor.postProcessEnvironment(this.environment, null); + assertThat(this.environment.getProperty("server.port")).isEqualTo("0"); + assertThat(this.environment.getProperty("management.server.port")).isEqualTo("0"); + } + + @Test + public void postProcessWhenTestServerPortIsZeroAndManagementPortMinusOne() { + addTestPropertySource("0", null); + Map other = new HashMap<>(); + other.put("management.server.port", "-1"); + MapPropertySource otherSource = new MapPropertySource("other", other); + this.propertySources.addLast(otherSource); + this.postProcessor.postProcessEnvironment(this.environment, null); + assertThat(this.environment.getProperty("server.port")).isEqualTo("0"); + assertThat(this.environment.getProperty("management.server.port")) + .isEqualTo("-1"); + } + + private void addTestPropertySource(String serverPort, String managementPort) { + Map source = new HashMap<>(); + source.put("server.port", serverPort); + source.put("management.server.port", managementPort); + MapPropertySource inlineTestSource = new MapPropertySource( + TestPropertySourceUtils.INLINED_PROPERTIES_PROPERTY_SOURCE_NAME, source); + this.propertySources.addFirst(inlineTestSource); + } + +}