From bf9d23e55a64f78537181788baa695f607ea2ecb Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 24 Sep 2020 14:11:43 +0100 Subject: [PATCH] Consider SpringBootTest's web environment in context cache key Previously, the web environment configured on `@SpringBootTest` was not part of the context cache key. As a result, two test classes that has identical configuration other than one using a MOCK web environment and the other using a DEFINED_PORT web environment would share a context when they should not do so. Classes that use MOCK and RANDOM_PORT were not affected as the use of RANDOM_PORT results in a property for the port being added to the environment. This commit adds a new ContextCustomizer, SpringBootTestWebEnvironment, that is used to capture the `webEnvironment` from `@SpringBootTest` and use it in its hashCode and equals implementations. This fixes the problem as all context customizers are evaluated when determing the equality of two context cache keys. Fixes gh-23085 --- .../SpringBootTestContextBootstrapper.java | 1 + .../context/SpringBootTestWebEnvironment.java | 57 +++++++++++++++++++ ...pringBootTestContextBootstrapperTests.java | 33 +++++++++++ 3 files changed, 91 insertions(+) create mode 100644 spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestWebEnvironment.java diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java index d8abb24333..06cf716883 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java @@ -359,6 +359,7 @@ public class SpringBootTestContextBootstrapper extends DefaultTestContextBootstr Class[] classes, String[] propertySourceProperties) { Set contextCustomizers = new LinkedHashSet<>(mergedConfig.getContextCustomizers()); contextCustomizers.add(new SpringBootTestArgs(mergedConfig.getTestClass())); + contextCustomizers.add(new SpringBootTestWebEnvironment(mergedConfig.getTestClass())); return new MergedContextConfiguration(mergedConfig.getTestClass(), mergedConfig.getLocations(), classes, mergedConfig.getContextInitializerClasses(), mergedConfig.getActiveProfiles(), mergedConfig.getPropertySourceLocations(), propertySourceProperties, contextCustomizers, diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestWebEnvironment.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestWebEnvironment.java new file mode 100644 index 0000000000..45a431f049 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestWebEnvironment.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2020 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.test.context; + +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.test.context.ContextCustomizer; +import org.springframework.test.context.MergedContextConfiguration; + +/** + * {@link ContextCustomizer} to track the web environment that is used in a + * {@link SpringBootTest}. The web environment is taken into account when evaluating a + * {@link MergedContextConfiguration} to determine if a context can be shared between + * tests. + * + * @author Andy Wilkinson + */ +class SpringBootTestWebEnvironment implements ContextCustomizer { + + private final WebEnvironment webEnvironment; + + SpringBootTestWebEnvironment(Class testClass) { + this.webEnvironment = MergedAnnotations.from(testClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY) + .get(SpringBootTest.class).getValue("webEnvironment", WebEnvironment.class).orElse(null); + } + + @Override + public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) { + } + + @Override + public boolean equals(Object obj) { + return (obj != null) && (getClass() == obj.getClass()) + && this.webEnvironment == ((SpringBootTestWebEnvironment) obj).webEnvironment; + } + + @Override + public int hashCode() { + return (this.webEnvironment != null) ? this.webEnvironment.hashCode() : 0; + } + +} diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperTests.java index db4261d1b7..a2e8513954 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/bootstrap/SpringBootTestContextBootstrapperTests.java @@ -71,6 +71,24 @@ class SpringBootTestContextBootstrapperTests { assertThat(contextConfiguration).isEqualTo(otherContextConfiguration); } + @Test + void mergedContextConfigurationWhenWebEnvironmentsDifferentShouldNotBeConsideredEqual() { + TestContext context = buildTestContext(SpringBootTestMockWebEnvironmentConfiguration.class); + Object contextConfiguration = ReflectionTestUtils.getField(context, "mergedContextConfiguration"); + TestContext otherContext = buildTestContext(SpringBootTestDefinedPortWebEnvironmentConfiguration.class); + Object otherContextConfiguration = ReflectionTestUtils.getField(otherContext, "mergedContextConfiguration"); + assertThat(contextConfiguration).isNotEqualTo(otherContextConfiguration); + } + + @Test + void mergedContextConfigurationWhenWebEnvironmentsSameShouldtBeConsideredEqual() { + TestContext context = buildTestContext(SpringBootTestMockWebEnvironmentConfiguration.class); + Object contextConfiguration = ReflectionTestUtils.getField(context, "mergedContextConfiguration"); + TestContext otherContext = buildTestContext(SpringBootTestAnotherMockWebEnvironmentConfiguration.class); + Object otherContextConfiguration = ReflectionTestUtils.getField(otherContext, "mergedContextConfiguration"); + assertThat(contextConfiguration).isEqualTo(otherContextConfiguration); + } + @SuppressWarnings("rawtypes") private TestContext buildTestContext(Class testClass) { SpringBootTestContextBootstrapper bootstrapper = new SpringBootTestContextBootstrapper(); @@ -99,6 +117,21 @@ class SpringBootTestContextBootstrapperTests { } + @SpringBootTest(webEnvironment = WebEnvironment.MOCK) + static class SpringBootTestMockWebEnvironmentConfiguration { + + } + + @SpringBootTest(webEnvironment = WebEnvironment.MOCK) + static class SpringBootTestAnotherMockWebEnvironmentConfiguration { + + } + + @SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT) + static class SpringBootTestDefinedPortWebEnvironmentConfiguration { + + } + @SpringBootTest(args = "--app.test=same") static class SpringBootTestSameArgsConfiguration {