mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-08-29 03:06:45 +08:00
Fix Mock|SpyBean context caching
The fix for gh-20916 updated DefinitionsParser so that the ResolvableType for each MockBean or SpyBean field included the implementation class from which the field was found. Where the field was declared with a variable generic signature that was made constant by its implementation class, this allowed the correct concrete type to be determined. It also had the unintended side-effect of preventing two test classes with identical `@MockBean` and `@SpyBean` configuration from sharing a context as the resolvable types for their mock and spy bean fields would now be different. This commit updates DefinitionsParser to only include the implementation class in the ResolvableType if the field's generic type is variable. For cases where it is not variable, this restores the behaviour prior to the fix for gh-20916. Fixes gh-22566
This commit is contained in:
parent
16eaae0b2f
commit
41954533b2
@ -18,6 +18,7 @@ package org.springframework.boot.test.mock.mockito;
|
|||||||
|
|
||||||
import java.lang.reflect.AnnotatedElement;
|
import java.lang.reflect.AnnotatedElement;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.TypeVariable;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
@ -114,7 +115,13 @@ class DefinitionsParser {
|
|||||||
types.add(ResolvableType.forClass(clazz));
|
types.add(ResolvableType.forClass(clazz));
|
||||||
}
|
}
|
||||||
if (types.isEmpty() && element instanceof Field) {
|
if (types.isEmpty() && element instanceof Field) {
|
||||||
types.add(ResolvableType.forField((Field) element, source));
|
Field field = (Field) element;
|
||||||
|
if (field.getGenericType() instanceof TypeVariable) {
|
||||||
|
types.add(ResolvableType.forField(field, source));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
types.add(ResolvableType.forField(field));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return types;
|
return types;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* 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.mock.mockito;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTestContextBootstrapper;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.test.context.BootstrapContext;
|
||||||
|
import org.springframework.test.context.MergedContextConfiguration;
|
||||||
|
import org.springframework.test.context.TestContext;
|
||||||
|
import org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate;
|
||||||
|
import org.springframework.test.context.cache.DefaultContextCache;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.BDDMockito.given;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for application context caching when using {@link MockBean @MockBean}.
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
*/
|
||||||
|
class MockBeanContextCachingTests {
|
||||||
|
|
||||||
|
private final DefaultContextCache contextCache = new DefaultContextCache();
|
||||||
|
|
||||||
|
private final DefaultCacheAwareContextLoaderDelegate delegate = new DefaultCacheAwareContextLoaderDelegate(
|
||||||
|
this.contextCache);
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
void clearCache() {
|
||||||
|
Map<MergedContextConfiguration, ApplicationContext> contexts = (Map<MergedContextConfiguration, ApplicationContext>) ReflectionTestUtils
|
||||||
|
.getField(this.contextCache, "contextMap");
|
||||||
|
for (ApplicationContext context : contexts.values()) {
|
||||||
|
if (context instanceof ConfigurableApplicationContext) {
|
||||||
|
((ConfigurableApplicationContext) context).close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.contextCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenThereIsANormalBeanAndAMockBeanThenTwoContextsAreCreated() {
|
||||||
|
bootstrapContext(TestClass.class);
|
||||||
|
assertThat(this.contextCache.size()).isEqualTo(1);
|
||||||
|
bootstrapContext(MockedBeanTestClass.class);
|
||||||
|
assertThat(this.contextCache.size()).isEqualTo(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenThereIsTheSameMockedBeanInEachTestClassThenOneContextIsCreated() {
|
||||||
|
bootstrapContext(MockedBeanTestClass.class);
|
||||||
|
assertThat(this.contextCache.size()).isEqualTo(1);
|
||||||
|
bootstrapContext(AnotherMockedBeanTestClass.class);
|
||||||
|
assertThat(this.contextCache.size()).isEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
private void bootstrapContext(Class<?> testClass) {
|
||||||
|
SpringBootTestContextBootstrapper bootstrapper = new SpringBootTestContextBootstrapper();
|
||||||
|
BootstrapContext bootstrapContext = mock(BootstrapContext.class);
|
||||||
|
given((Class) bootstrapContext.getTestClass()).willReturn(testClass);
|
||||||
|
bootstrapper.setBootstrapContext(bootstrapContext);
|
||||||
|
given(bootstrapContext.getCacheAwareContextLoaderDelegate()).willReturn(this.delegate);
|
||||||
|
TestContext testContext = bootstrapper.buildTestContext();
|
||||||
|
testContext.getApplicationContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SpringBootTest(classes = TestConfiguration.class)
|
||||||
|
static class TestClass {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@SpringBootTest(classes = TestConfiguration.class)
|
||||||
|
static class MockedBeanTestClass {
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private TestBean testBean;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@SpringBootTest(classes = TestConfiguration.class)
|
||||||
|
static class AnotherMockedBeanTestClass {
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private TestBean testBean;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
static class TestConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
TestBean testBean() {
|
||||||
|
return new TestBean();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class TestBean {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user