diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/DefinitionsParser.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/DefinitionsParser.java index 9028b70815d..8b73e40d7e4 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/DefinitionsParser.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/DefinitionsParser.java @@ -60,20 +60,20 @@ class DefinitionsParser { } void parse(Class source) { - parseElement(source); - ReflectionUtils.doWithFields(source, this::parseElement); + parseElement(source, null); + ReflectionUtils.doWithFields(source, (element) -> parseElement(element, source)); } - private void parseElement(AnnotatedElement element) { + private void parseElement(AnnotatedElement element, Class source) { MergedAnnotations annotations = MergedAnnotations.from(element, SearchStrategy.SUPERCLASS); annotations.stream(MockBean.class).map(MergedAnnotation::synthesize) - .forEach((annotation) -> parseMockBeanAnnotation(annotation, element)); + .forEach((annotation) -> parseMockBeanAnnotation(annotation, element, source)); annotations.stream(SpyBean.class).map(MergedAnnotation::synthesize) - .forEach((annotation) -> parseSpyBeanAnnotation(annotation, element)); + .forEach((annotation) -> parseSpyBeanAnnotation(annotation, element, source)); } - private void parseMockBeanAnnotation(MockBean annotation, AnnotatedElement element) { - Set typesToMock = getOrDeduceTypes(element, annotation.value()); + private void parseMockBeanAnnotation(MockBean annotation, AnnotatedElement element, Class source) { + Set typesToMock = getOrDeduceTypes(element, annotation.value(), source); Assert.state(!typesToMock.isEmpty(), () -> "Unable to deduce type to mock from " + element); if (StringUtils.hasLength(annotation.name())) { Assert.state(typesToMock.size() == 1, "The name attribute can only be used when mocking a single class"); @@ -86,8 +86,8 @@ class DefinitionsParser { } } - private void parseSpyBeanAnnotation(SpyBean annotation, AnnotatedElement element) { - Set typesToSpy = getOrDeduceTypes(element, annotation.value()); + private void parseSpyBeanAnnotation(SpyBean annotation, AnnotatedElement element, Class source) { + Set typesToSpy = getOrDeduceTypes(element, annotation.value(), source); Assert.state(!typesToSpy.isEmpty(), () -> "Unable to deduce type to spy from " + element); if (StringUtils.hasLength(annotation.name())) { Assert.state(typesToSpy.size() == 1, "The name attribute can only be used when spying a single class"); @@ -108,13 +108,13 @@ class DefinitionsParser { } } - private Set getOrDeduceTypes(AnnotatedElement element, Class[] value) { + private Set getOrDeduceTypes(AnnotatedElement element, Class[] value, Class source) { Set types = new LinkedHashSet<>(); for (Class clazz : value) { types.add(ResolvableType.forClass(clazz)); } if (types.isEmpty() && element instanceof Field) { - types.add(ResolvableType.forField((Field) element)); + types.add(ResolvableType.forField((Field) element, source)); } return types; } diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/AbstractMockBeanOnGenericExtensionTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/AbstractMockBeanOnGenericExtensionTests.java new file mode 100644 index 00000000000..8481e999bd5 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/AbstractMockBeanOnGenericExtensionTests.java @@ -0,0 +1,27 @@ +/* + * 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; + +/** + * Concrete implementation of {@link AbstractMockBeanOnGenericTests}. + * + * @author Madhura Bhave + */ +class AbstractMockBeanOnGenericExtensionTests extends + AbstractMockBeanOnGenericTests { + +} diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/AbstractMockBeanOnGenericTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/AbstractMockBeanOnGenericTests.java new file mode 100644 index 00000000000..76ae636c574 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/AbstractMockBeanOnGenericTests.java @@ -0,0 +1,84 @@ +/* + * 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 org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link MockBean} with abstract class and generics. + * + * @author Madhura Bhave + */ +@SpringBootTest(classes = AbstractMockBeanOnGenericTests.TestConfiguration.class) +abstract class AbstractMockBeanOnGenericTests, U extends AbstractMockBeanOnGenericTests.Something> { + + @Autowired + private T thing; + + @MockBean + private U something; + + @Test + void mockBeanShouldResolveConcreteType() { + assertThat(this.something).isInstanceOf(SomethingImpl.class); + } + + abstract static class Thing { + + @Autowired + private T something; + + T getSomething() { + return this.something; + } + + void setSomething(T something) { + this.something = something; + } + + } + + static class SomethingImpl extends Something { + + } + + static class ThingImpl extends Thing { + + } + + static class Something { + + } + + @Configuration + static class TestConfiguration { + + @Bean + ThingImpl thing() { + return new ThingImpl(); + } + + } + +}